Thursday, August 24, 2006

One at a Time


I'd love to be writing more here, but instead I'm putting my Mulgara ideas up on the Mulgara Wiki. Anyone interested should look under Development/Indexes.

I can only really manage the time for writing in one place at a time. Not to say that I'm abandoning this blog, but it is more appropriate to put Mulgara design details on the Wiki.

Saturday, August 19, 2006

Mulgara v1.0.0


Mulgara v1.0.0 has been released!

All my spare time has gone into this recently, which should explain the dearth of blogs posts. Now that the first release is out, I should have more time to:
  • Write some code.
  • Write more blog entries.
  • Get OWL working in Mulgara.
I hope that future releases will require less effort, since the project is off the ground, and we've established several procedures for this first release.

Thanks to the people who helped me get it out. In particular:
  • Brian, who coded and debugged.
  • David, who reviewed everything from legal files through to web pages, and without whom I'd find myself in a world of trouble.
  • Thomas, who volunteered out of nowhere to design the web pages for us.


For anyone missing my technical musings on the structure of Mulgara, then have a look at the Mulgara Wiki. I've just started using this area, so others can contribute, and also because it allows me to structure some of the details.

Saturday, July 08, 2006

Forking Code


Of course, I can't really discuss work online, which has been a good reason to keep quiet lately. (Since I'm working so much of the time). The other reason is because my spare time is kept occupied with Mulgara.

Anyone looking at Mulgara recently would have some idea of what I'm been doing recently, instead of blogging. The short story is that NGC have come to a legal agreement acknowledging that Kowari is open source, and they should not have stopped progress in the project.

There are some who hope we can now abandon the fork, and go back to working on Kowari. In one sense, I'd certainly like that. Kowari has a name for itself, and it's a name I've worked with for years. It helps people who have a commitment to using Kowari, and for whom a name change would be an annoying distraction.

On the other hand, NGC didn't have a legal leg to stand on when they started all of this, and yet they demonstrated that you don't need one to cause problems. The problems have all come down to misunderstandings, but Open Source Software has always been difficult for NGC to understand, and it is always possible they will have difficulty understanding it in the future. I know they also offended some of the Kowari developers while mendaciously describing our work, and that never goes over well with people.

So while it would be a terrible shame to lose the name Kowari, there areenough people agreeing that there are enough reasons to go ahead with Mulgara. Besides, having started it up and got it running now (many thanks to Luigi and Herzum Software for the support) I feel I have some ownership over the process in the new project.

The other thing people might notice is that I've been working on code. Alas, there are no new features yet, but I'm getting close to a point where they will be forthcoming.

Roughly, the Mulgara work falls into 3 categories:
  • Administration for the project.
  • Forking the Kowari sources.
  • Upgrading Mulgara from Java 1.4 to Java 1.5.


Administration means learning about, and setting up Mailman, learning how to configure and use Subversion, learning the latest changes in Apache configurations (I think I last configured Apache at version 1.3), and setting up HTTPS and WebDAV access. Yuck.

I still haven't worked out how to get WebDAV to force logins over HTTPS. I can enforce logins, and I can get it over HTTPS, but not both. Oh well. I guess it just means that administration has to be done by Jesse or myself for teh time being.

Forking Mulgara has had its own difficulties. We just about had it done a few weeks ago, but NGC started insisting that certain recent code belonged to them and should not have gone into CVS at Sourceforge. They've since retracted this claim, but in the meantime we went back to the 1st of August last year and forked from that point. So we had to repeat a lot of the work we'd already done. Very annoying.

One benefit to going back to August has been that we may get to avoid a bug that we think has been introduced since then. A couple of users have reported difficulty loading large RDF/XML files. Since the parsing has all changed since August (not as a fix, but in an attempt to avoid using Jena), then this could be the cause of the problem. It will be a good opportunity to check it out anyway.

A number of bugs were introduced when all the packages, files and variables were renamed in the fork. In the course of tracking down all these bugs, I've used the opportunity to move to Java 1.5. It just created a few more bugs to track down. Fortunately, I knew where to find most of these problems from earlier personal attempts to convert to Java 1.5.

Java 1.5


The most obvious issue moving to Java 1.5 has been the new keyword "enum". I've noticed in several other places that "enum" is a popular name for packages and variables. Kowari was no exception. This was easily remedied. Unfortunately Apache has a package called enum which is referenced by code generated by the WSDL compiler. The workaround is to compile just this generated source with the -source 1.4 flag.

The other problem I encountered was in the change to Unicode 4.0.

I never gave a lot of thought to Unicode before Kowari was released as Open Source Software. Until that point I'd been able to get away with ASCII. After all, I don't know any other spoken languages (I really ought to do something about that) and all my work had been in English.

After Kowari had been out for a little while we started getting bug reports and fixes from someone in China compaining about how Kowari's strings were not handling certain characters properly. This woke me up to how international OSS is. I was used to working with people in the United States, but by being involved with something like this on the internet we were exposed to a whole new international community.

This has made me a little more sensitive to the need to support Unicode in all projects. OSS projects need it for the reasons I mentioned above, and commercial projects need it if they ever hope to succeed internationally (a real concern for me when my employer has offices in Italy, the UK, Turkey, and France).

Now 16 bit characters are all well and good, but the latest version of Unicode requires a full 20 bits. These extra characters (called "supplemental" characters) are not used very much, but I've already learnt that we can't ignore the things we are ignorant of. Unfortunately, the previous mechanism for managing these characters would not work in Java 1.5.

Previously we had used a regular expression to look for a pair of "surrogate" characters, which together form a single Supplemental Unicode character. Unfortunately, the character classes which used to be supported for this in Java 1.4 are no longer available. Some research showed that the regex library now wanted to treat these characters as a single character (which is a definite improvement), but checking documentation, and finally Java source code showed that there is no "character class" for referring to the supplemental character set.

I'm short on time here, but for anyone interested, the old regular expression for this was:
"\\p{InHighSurrogates}\\p{InLowSurrogates}"

Now the correct expression is to use the numeric range for the surrogates, and use them to build a class of unicode literals which span this region (U00010000 to U0010ffff):
"[\ud800\udc00-\udbff\udfff]"

This seems to be the worst of all worlds. We have to use a pair of 16 bit codes to create a single 20 bit unicode (using the surrogate arithmetic for calculating the final value. e.g. UD800+UDC00 = U00010000). However, we can no longer refer to these codes as a "pair of surrogates", but as a single character (even though we DEFINE that character with a 2-part code). It might have been nice if Sun created a character class like this for us. Something like:
"\\p{InSupplementals}"

Well, at least it now works in Java 1.5. It just doesn't work in Java 1.4.

geo:(41.8851942,-87.6351607)
mylocalguru

Monday, May 15, 2006

Subversion


For anyone not familiar with subversion (I include myself in this group) then the 1.2 version of the code can be retrieved via:
  svn co https://mulgara.org/svn/mulgara/trunk/kowari-1.2

I just thought I'd mention this in case anyone tries to get the whole repository (which includes the entire history of the project). The entire thing comes in at 2.6 GB. The latest branch is a more modest 204 MB.

Thursday, May 11, 2006

Mulgara

The two most important elements of Mulgara.org are now running: Mailing lists and Subversion.

I've discovered that Yum is not the way to install Mailman on Fedora Core 4. It isn't integrated with any MTA which means that it needs a lot of manual configuration. After getting it working with Exim, I discovered that it wouldn't send any messages due to a problem with Sendmail. Moving from Exim to Postfix fixed the problem, but involved reconfiguring Mailman. Overall, a frustrating experience. I'm very grateful to Jesse for his help.

Jesse is the official administrator of mulgara.org, but like me he has other work to get done. Since I'm the one who's actually developing for Mulgara, he was more than happy to give me sudo and let me install the things I needed. The only problem is that he knows what he's doing, and I don't. :-)

Services

So where are we? We have:

Still to do:
  • The web page is currently just a place holder. We need to come up with content, and we need to move all the documentation over to it.
  • From a development point of view, we need to move all the org.kowari packages over to org.mulgara. This would be easier with the refactoring tools in Eclipse, but these won't use the required svn mv command. Much of the first round will be a simple search/replace, so I may just be dusting off my perl/sed skills. To anyone with suggestions on how to do this more intelligently, please get in touch!
  • The site needs a signed certificate. Thawte and GoDaddy both have certificates for under $100, but I should check out with other what we really need. I'm guessing we only need the bare minimum.
  • We need a Wiki and bug reporting system. Herzum Software has offered to provide licensed copies of Atlassian's Jira and Confluence for use by the project. I'll have to ask Jesse to install these, as I have no idea about either of them.


Feel free to write to me with any questions, advice or complaints. I'll answer, accept, and listen, respectively.

Wednesday, May 10, 2006

Work, work , work

Yeach. Long hours in the last few weeks have left me feeling wrung out. I tried to make time for family through this (not always successfully), so I didn't have time for anything else. The first thing I tried to catch up on afterwards was exercise. This blog came way down in the list of priorities.

Bugs

I discovered a couple of problems in Kowari in the last couple of weeks. Unfortunately I had goals to push towards, so I haven't been able to look at them yet. I'll get back to them, but in the meantime I'll write them down here for others to look at.

Full Text Searching

One thing I wanted to do (fortunately I didn't need to do it at the time) was full text indexing and querying. This is straightforward using a Lucene model. Unfortunately I couldn't get it to work! All my queries came up with empty results.

I figured that I must have been doing something wrong, so I went to the examples in the documentation. I couldn't get any results here as well. So now I think that something must be wrong. I haven't had an opportunity to run the full test suite lately (I really only have a notebook, and it's slow enough without having to run the tests in the background), but I'll have to run it soon to make sure everything is still OK. If it is, then I'll be running Kowari through a debugger to figure out why I can't get any results from Lucene models.

Normally I'd have my big Linux box doing these tests for me, but it's still in a shipping container, and I've been told that I won't see it before June. Ask me if I'm frustrated.

There is one other machine that I could use, and that's the Mulgara server. I haven't discussed Mulgara here, so I'll discuss it more later.

Datatypes

This bug doesn't affect me at all (at the moment), but it's surprising to see all the same. I discovered this bug when scanning an model which restricted a class using an owl:cardinality of "1". The data type for owl:cardinality is xsd:nonNegativeInteger, so that's what I used. So far, so good.

Then in some test code, I inserted a series of numbers into a Sequence. These were just 1, 2 and 3, and each of them was typed as xsd:int. However, when looking at the full contents of the model I was surprised to see the following numbers in the sequence:
  "3"^^<http://www.w3.org/2001/XMLSchema#int>
"2"^^<http://www.w3.org/2001/XMLSchema#int>
"1"^^<http://www.w3.org/2001/XMLSchema#nonNegativeInteger>
So if the number was already in the string pool as a "nonNegativeInteger", then it just picks up that value again, rather than re-inserting it.

This will be OK for simple cases like this, but it won't work in the general case. Looking at the hierarchy of XSD data types you can see that int and nonNegativeInteger are both subtypes of integer, but they are not in the same branch. So neither can be used in the place of the other, unless an application specifically calls for an integer (their closest common ancestor).

So having the string pool automatically convert one to the other is going to be a problem one day. I've already started checking literal data types, and need to make an exception for this case. I can certainly conceive of a system which would fail a necessary consistency check because of this.

Now I'm left with the option of fixing it now, or waiting to put it into the XA2 store. I still don't know when we'll have time to get to XA2, so maybe an interim fix is the way to go.

RMI Connections

By far my biggest problem is RMI connections. I've seen posts complaining about how slow Kowari is, which I find really frustrating. Queries in Kowari are nearly instantaneous, and insertions are pretty fast as well. So why do people think it's slow? The answer lies somewhere in the Session interface.

For some reason, when the first query is performed on a Kowari database, the Session can take several seconds to establish a connection. This happens whether or not the system is remote or local. I have yet to profile this delay, but it all seems to be in the RMI code. I can't imagine that RMI is that bad, so I'm guessing that it's doing a DNS lookup in there, though I may be wrong. Either day, it's an unacceptable delay. We need some way to speed this up, particularly when running on a local machine. I put a lot of work into making Kowari really, really fast, and the frustration I feel at these network layer delays nearly drives me to tears.

Another problem is the what happens with an error from the ItqlInterpreterBean. It seems that a single error (often avoidable as they are normally caused by programming bugs) can render the Session nearly useless. There is some kind of synchronization issue after an Exception is thrown, that a Session can't seem to overcome, at least for a couple of operations after that. I'm not sure why this is, but it bears investigating.

A lot of these problems seem to come down to RMI. I don't know what the best replacement technology would be (SOAP will also have DNS issues, etc) but we need to look at something. Whatever we choose, we will probably need to bypass it when accessing a local data store. At the moment we do this transparently, but perhaps we should consider something a little more optimized for local connections, since this forms the majority of the use cases I've seen.

Mulgara

As David Wood pointed out, we are forking the Kowari Project. The new project will be called "Mulgara". We felt this was necessary to keep developing the RDF database when there was uncertainty due to the relationship between Kowari and Northrop Grumman.

Some of the problems appear to be caused by a misunderstanding of Open Source Software by Northrop. This has made me reluctant to discuss Mulgara until now, as I didn't want further misunderstandings to develop. Fortunately, we've had legal advice from several quarters confirming our right to fork the project (since the project is licensed under the MPL).

My employer, Herzum Software, has offered to host the new project, putting up a new Linux box for this purpose (running Fedora Core 4). Unfortunately, the administrator for the machine has been overseas for the last couple of weeks, and with my recent long hours neither of us has had time to configure all of the necessary services. I hope to change that this week. In particular, I've discovered that Yum is not a very good tool for installing Mailman (it didn't even notice that there was no MTA installed!)

Some of the services are already going, such as Subversion, but I still need to organize a certificate (at the moment it just provides the "localhost" certificate that comes standard with Fedora Core).

Check back soon and we should have it all running.

Tuesday, April 18, 2006

Commitments


It's quite difficult to blog while working and moving residence at the same time. I've had some big things happening at work at the same time, so the preference is usually to get those things done rather than write.

On the other hand, I've let slip so many things that I wanted to talk about, that I'm getting frustrated with my lack of blogging. I don't have a lot of time tonight, but I thought I should at least get something written.

FAT_MAGIC


Following a link off Slashdot, I found an interesting discussion of otool in OS X. The author had been looking for an ldd replacement for finding the dynamically linked libraries that a binary uses, which is something I've wanted as well. I just didn't know about otool.

otool seems to be a Swiss-army-knife for inspecting Mach binary files. The array of switches seem quite large, particularly since each function seems quite unrelated to most of the others. It makes the tool feel a little overloaded.

The article points out that the file /usr/bin/crt1.o contains the entry point code for all programs in OS X. I've been interested in the structure of universal binaries, particularly dynamic libraries, so this seemed like a good library to apply otool to.

Looking at the numerous parameters, I discovered that:
  otool -f
and
  lipo -detailed_info
produce nearly identical output. I suppose this makes sense, given that both are looking at the fields in standard Mach binary structures, but it's so close that I have to wonder if they might share some code. If that is the case, then I wonder if any other otool flags duplicate the functionality of other programs?

While looking at all the information available from otool, I discovered that all of the programs I was looking at start with a magic number of: 0xcafebabe. I was shocked at this, as this is the same magic number used by Java class files. Why would they share this value?

I suspect that FreeBSD took this number first with their definition of the Mach file format, and Sun's Java team weren't aware of it when they were defining Java. Or perhaps they knew, but didn't care about a small, non-commercial operating system (little did anyone know what would become of FreeBSD in the future!). So where did FreeBSD get this format from? Did they define it themselves, or did they inherit it?

Whatever the reason, it obviously created conflict. I found a reference to it in the change log for the cctools package in the Darwin project:
Changes for the 5.16 release (the cctools-520 release):
- Fixed a bug that caused otool(1) to crash when it was called with a bad fat
file (a java class file). The calls to ofile_first_arch() in ofile.c was not
checking its return value and later causing the code to crash when it should
have returned in case of an error. Radar bug #3670740.
Looking at the code in question, it now looks for bytes at a particular offset to try to avoid mistaking a Java class file for a Mach binary. I don't know if it's justified, but I always feel uneasy when a programmer leaves a note to say that the implemented approach is a hack.

These magic numbers are supposed to help a system differentiate between different file types, particularly executables. Having the same number on two major file types seems like a problem, but it's not one that could be changed now.

Friday, March 31, 2006

Programming


I've actually been doing some programming at home lately, which seriously cuts into the time in which I might blog. At least I've felt productive. That may have to take a back seat for a few days, as we're moving apartments this weekend.

Brian at work has created a small auditing library, which can be called by developers to log when methods are entered and exited. The cool thing was that he has turned the results into some cool interactive SVG that plots the course of the program.

At this point I remembered that Java 1.5 now includes instrumentation. If this could be used to automate the calling of the auditing methods, then this would remove the burden from the programmer (who, lets face it, could get it wrong, or just be too lazy to bother). So I started learning a little about the new interfaces.

It turns out that instrumentation is quite simple. In fact, it's too simple. Look at the size of the API, and you'll see that there are only a couple of methods. All it does is inform you when a class is being loaded, and provides you with read/write access to the bytecode for that class. That's about it. If you want to take it any further, then you have to get your hands dirty and manipulate the bytecode.

Fortunately the articles I found online did just this. The library generally used for this is ASM. There are several others, but ASM has the advantage of being simple and lightweight.

ASM also has one killer feature that I have yet to see in other bytecode manipulation libraries: an Eclipse plugin with some great features. This plugin not only lets you view the bytecode of your current code, but it also lets you switch to a view where you can see the ASM code required to generate the bytecode for your current code. So all you have to do is type in an example of the code you'd like to generate the bytecode for, and ASM will show you the ASM library calls you make. This almost makes coding with ASM trivial.

So I've been using ASM to instrument code, and generating pretty SVG plots of the call stack over time. I've learnt quite a bit about Java class loading in the process. It's also moved me one step closer to being able to generate classes out of OWL descriptions (yes, I can use UML, but that's not very serializable now, is it?). Probably the biggest trick was working out how to not audit the code doing the auditing (I hit some infinite recursion on my first attempt). However, this ended up being trivial when I realised that I'm just auditing a stack, which by definition is associated with a thread (Brian's library records method entries and exits based on the current thread). All I needed was some thread-local-storage to hold a lock on the auditing process. Then if the auditing code saw that the lock was being held, it skipped the auditing and returned to the original task. The best part is that its all local to the current thread, so there's no synchronisation required.

This kind of bytecode manipulation was amazingly similar to the Windows API interception I was doing a couple of years ago. I enjoyed it then too. It was just as tough to debug as well, since debuggers don't expect to see bytecodes modified from the condition the compiler left them in. The classes drop all line numbers, and tracepoints only start to make sense when the instruction pointer moves to an unaffected class. I've also started running into the following exception when I use instrumentation on code with a logger in it:
java.util.logging.ErrorManager: 5
java.util.MissingResourceException: Can't find bundle for base name sun.text.resources.DateFormatZoneData, locale en_US
I then get shown a deep stack, which would seem to indicate that the method-calling modifications from ASM are all correct. So it looks like the classpath is wrong at this point. Frankly I'm not surprised, as I've seen different classloaders coming in during the startup process of an application (it's odd seeing an exception about a missing class when the code in question is being called from inside the class that can't be found!

Still, if I avoid the logger, then all seems well.

Wednesday, March 15, 2006

Macs


I've had some new toys to play with in the last few days, so my documentation of Kowari has slowed down. I expect to be back to it in a couple of days (though not tonight, and tomorrow is Anne's birthday).

In the meantime, I've been looking at compiling things in Eclipse on the Mac.

For a start, our new Mac Mini (which runs Java code 4 times faster than my PowerBook!) can't run Eclipse. The lack of an Intel version is listed as a bug, but it hasn't been addressed yet. As I type this I have a set of scripts downloading ALL the Eclipse source code, in the hope that I can find the JNI libraries I need. So far I've found one of the 6 I need.

This was all so I could try compiling a big project from work in an Eclipse environment.

Finding Files


While setting up the files for this project from work, I discovered that a Jar file was missing from the build path. I still like to use locate to look for files by name, as Spotlight only seems to be any good when looking for files by content. However, since the file had only just come in through CVS, it wasn't going to be in the locate database.

Of course, the simple thing to do here was to run find, but it reminded me that I wanted to update the locate database. Of course, I couldn't remember the name, as it's different to the simple updatedb found on Linux. Fortunately "locate updatedb" helped be remember that I was looking for locate.updatedb.

This led me to wonder why the update doesn't happen much on this machine. The computer is often on overnight, yet locate regularly fails to find files I know I've had for a while. I know that something has supplanted cron on the Mac, so I did a man locate.updatedb. This pointed me to the cron directory of /etc/weekly, so I decided to check who was supposed to call it and when.

/etc/weekly is a symlink to /etc/periodic/500.weekly, so I grepped through /etc and discovered:
    crontab:# The periodic and atrun jobs have moved to launchd jobs
Now this looks like the cron replacement! Running which launchd returned /sbin/launchd, which did little more than tell me it is an admin command (obviously). So I checked out the man pages.

The man pages explain that launchd is a new system to replace rc, though there are still a couple of elements going through the old rc system. launchd is configured with XML (as are most of the modern features in OSX), using files found in the following directories:
     ~/Library/LaunchAgents         Per-user agents provided by the user.
/Library/LaunchAgents Per-user agents provided by the administrator.
/Library/LaunchDaemons System wide daemons provided by the administrator.
/System/Library/LaunchAgents Mac OS X Per-user agents.
/System/Library/LaunchDaemons Mac OS X System wide daemons.
Examples are quicker to look at than documentation, so I checked these out, and discovered that all but the final directory was empty. However, the contents here included a file called com.apple.periodic-monthly.plist which appeared to be what I was after. Looking inside I found that it was all XML, as advertised, including the following fragment:
  <key>ProgramArguments</key>
<array>
<string>/usr/sbin/periodic</string>
<string>weekly</string>
</array>
Explanations of this and the rest of the file are found in man launchd.plist. But even without looking in the man pages I could see that it was calling a program called periodic to do the work when the time arrived.

So now I knew that launchd did the scheduling, and I could change the time if necessary by using the XML file /System/Library/LaunchDaemons/com.apple.periodic-monthly.plist.

But what if I wanted to run the process manually (like, right now)? So I ran file /usr/sbin/periodic, and was told:
/usr/sbin/periodic: Bourne shell script text executable
A quick look inside this script showed that it takes an argument of "daily", "weekly" or "monthly", with predictable results.

So I could run periodic directly, but the man pages from earlier made me wonder if I can do this with a "control" program for launchd called launchctl?

Looking at the man pages again told me that launchctl could be run interactively, which sounded like a better way to test things out. The program seemed unresponsive until I told it to "load /System/Library/LaunchDaemons", which got things moving along nicely. I'm still not sure if these processes were loaded before I put them in, and I didn't want to reboot to see if the clean machine had them.

I started a few of the processes, but 3 of them would not launch, and the system kept respawning them. I was worried that this try/die loop was going to go on indefinitely (or until I learned how to stop it), but fortunately the system worked out what was happening, and stopped them all.

In summary, it seems that launchd turns non-terminating programs that communicate with stdin/stdout, and turns them into logged daemons. Having a process that finishes quickly (like periodic) doesn't seem to be what it's for. So to run periodic manually I needed to call it directly.

That was a long winded way of making sure my locate database stays up to date, but I'm pleased I learned something about the system while I was at it. :-)

Wednesday, March 08, 2006

Documentation

Apologies to anyone not interested in reading my description of the inner workings of Kowari.

There's obviously a lot happening while I try to get used to a new city and lifestyle, so I haven't returned to my research yet. I'm also working on commercial code, which means I have to be more circumspect about what I'm doing. This all means I haven't had the time or motivation to do new things in my own time, making it hard to write about anything interesting!

As an exercise in re-familiarizing myself with Kowari, and keeping in the habit of writing (which can be hard to start again once you've stopped) I decided that I should write about how each class works. I mentioned this shortly after presenting all of this for NGC, but it's taken me this long to get around to it.

I'll write about more interesting things as they come to hand. For instance, I was told yesterday that a major impediment to the adoption of semantics in some areas has been the lack of a scalable statement store. Argh!

AbstractBlockFile

This class does not add much to the semantics of BlockFile, but it has a few internal details worth mentioning.

AbstractBlockFile contains a static block which determines the byte ordering to be used for all files. This is generally the native format for the platform, but maybe overridden by setting the system property tucana.xa.useByteOrder to native (the default), big_endian, or little_endian. This should usually be left alone, but it may be necessary if files have been moved from one computer to another with a different CPU architecture. Upgrading an Apple from PowerPC to the new Intel Core Duo is an good example of the need for this option.

The constructor for AbstractBlockFile performs several checks. It starts by checking if the file is already present in a static set of files. If so, then this JVM has already opened the file, so an exception is thrown. Otherwise, the file is added to the set once it has been opened.

The second check in the constructor is to ensure that the file is a multiple of the block size. If not, then it tries to truncate the file to an appropriate length.

Truncation is an awkward operation for a mapped file, as a mapping could still exist from the file being opened and subsequently closed earlier in the lifecycle of the program. If this is the case, an IOException will be thrown, and the mapping must be removed. Running the garbage collector is usually enough to clean up any unused file mappings, but on Windows it is necessary to run the collector several times for this technique to work.

The code to do this looks like:
for (;;) { 
try {
fc.truncate(fileSize);
break;
} catch (IOException ex) {
if (retries-- == 0) {
logger.error(
"Could not truncate file: \"" + file +
"\" to size: " + fileSize, ex
);
throw ex;
}
System.gc();
try { Thread.sleep(100); } catch (InterruptedException ie) { }
System.runFinalization();
}
}
This technique is duplicated in clear(), close() and delete().

Factory Methods

The openBlockFile() methods each take an IOType to determine which of the implementing classes to return an instance of. However, these methods also check the tucana.xa.forceIOType system property to see if this value should be overridden. If the property is set to mapped or explicit then that will dictate if MappedBlockFile or IOBlockFile should be used. Otherwise, the requested IOType will be used.

It should be noted that it is unusual for an abstract class to have information about its implementing subclasses like this. If another type of implementation were to be added, then this super class would need to be updated as well. This sort of dependency is undesirable in object oriented code, but the benefits here were deemed to make it worthwhile.

Opened Files

The constructor for this class does not open the file, but rather calls the ensureOpen() method. This method makes sure that a file is open, and if it is not, then it attempts to perform the open operation. The operation is called in this way to try to cover occasions where a problem occurred (such as a "rollback" operation), and there is some code in the method to handle these conditions.

Several other methods in AbstractBlockFile also call ensureOpen before performing file operations, in case anything has happened to close access to the file.

Note also, that access to ensureOpen and the isOpen set is all synchronized.

Tuesday, March 07, 2006

BlockFile

BlockFile is an interface for accessing files full of identically sized "blocks" of data. This interface is like IntFile, in that they both describe the abstract methods for accessing their respective files, and both have two concrete implementations: one for mapped file access, and the other for normal I/O access. In the case of BlockFile these are MappedBlockFile and IOBlockFile.

Unlike IntFile, the concrete implementations for BlockFile share a significant portion of their code. This is shared between the two classes by having them both extend an abstract class called AbstractBlockFile, which in turn implements BlockFile. So MappedBlockFile and IOBlockFile do not implement BlockFile directly.

Redundant Interfaces

This arrangement makes BlockFile superfluous, since AbstractBlockFile can be used in every context where BlockFile might have been. However, using an interface in this way is a well established technique in Kowari. It allows for the rapid development of alternative implementations as the need arises. It also permits an object that fulfils a number of roles to implement more than one interface. These advantages both help the codebase stay flexible for ongoing development.

Finally, these interfaces, redundant or not, provide a consistent way of documenting the method of using a class (its platonic "interface", as opposed to the grammatical construct in the Java language).

Records

The point of a BlockFile is to store a series of records of identical structure in a file. Each record will be exactly the same size. The data structure within that record is left up to the caller. Kowari uses BlockFiles to store numerous data types, including strings (of defined lengths), numbers, tree nodes, and linked lists.

Files which hold records like this are essential to scalable systems, as data from anywhere in the system can be found and modified rapidly. Variable length structures can be more space efficient, but require that a file be traversed before data can be found, and modification is often impossible. Variable length structures can be indexed for rapid retrieval, but modification is often impractical.

As a side note, the expected replacement for the Kowari file system will use variable length structures, but with inherent indexing to make traversal faster than it currently is. The problem with modifying data will still exist, but this will be managed in a linear annotation queue, which will be merged back in to the main data silently in the background. The result should demonstrate much faster read and write operations, particularly for terabyte scale files.

Methods

setNrBlocks() andgetNrBlocks() are used to set and get the length of the block file. Since the file may only be viewed as a collection of blocks, then its length should only be measured in units of blocks.

Like all the other file-manipulating classes, clear() is used to remove all data from the file, and set it to zero length. force() is used to block the process until all data is written to disk. unmap() attempts to remove any existing file mappings, close() closes the file, and delete() closes the file and removes it from the disk. See IntFile for a more detailed explanation.

The remaining methods are all for manipulating the blocks within this file.

Blocks

The Block class was created to abstract away access to the records stored within a BlockFile. The block is always tied back to its location on disk, so any operations on that block will always be to the specific location on disk. This is important for memory mapped files, as it means that a write to a block will be immediately reflected on disk (though disk I/O can be delayed by the operating system) without performing any explicit write operations with the block.

Implementations of BlockFile are also expected to manage Blocks through an object pool. This was particularly important in earlier JVMs, where object allocation and garbage collection were expensive operations. Fortunately, recent versions of Java no longer suffer these effects, and an object pool can even slow a process down.

The other reason to use an object pool for blocks is to prevent the need to re-read any blocks still in memory. This is not an issue for memory mapped files, but if the blocks are accessed with read/write operations then they need to be initialized by reading from disk. The object pool for blocks will re-use a block associated with a particular file address whenever one is available. If one is not available, then the standard pool technique of re-initializing an existing object is used. With Java 1.5, it is this last operation which may be discarded.

As a consequence of the above, the remaining BlockFile methods all accept an ObjectPool as a parameter. This may appear unnecessary, since a pool would usually be associated with an instance of the BlockFile class, but in this case there are several pools which may be used, depending on the needs of the calling code.

Block Methods

allocateBlock() is used to get a Block for a particular file position, defined by a blockId parameter, but not to read the file for any existing data. This is for new positions in the file where the data was previously unused. There is no reason to read the data at this point, and the read operation will be time consuming. Writing the resulting block will put its contents into the file at the given block ID.

readBlock is similar to allocateBlock, but the block is already expected to have data in it. The Block object is allocated and associated with the appropriate point in the file, and data is read into it.

Since read operations are never explicit in memory mapped files, the allocateBlock() and readBlock() methods are identical in the MappedBlockFile class.

Any blocks obtained from allocateBlock() and readBlock() should be passed to releaseBlock() once they have been finished with. This allows the block to be put back into the object pool. While not strictly necessary, it should improve performance.

writeBlock() writes the contents of a Block's buffer to that Block's location on disk. This performs a write() in IOBlockFile and is an empty operation (a no-op) in MappedBlockFile.

The copyBlock() method sets a new block ID on a given Block. This now means that the same data is set for a new block. On IOBlockFile this is a simple operation, as the buffer remains unchanged. However, on MappedBlockFile the contents of the first block have to be copied into the new block's position. This is unusual, since MappedBlockFile usually does less work in most of its methods.

writeBlock simply writes a block's buffer back to disk. This is a no-op in MappedBlockFile.

modifyBlock() and freeBlock() should be removed from this class, as they are operations which are designed for ManagedBlockFile. This class provides a layer of abstraction over BlockFile and will be described later. The only implementations of these methods is in AbstractBlockFile where an UnsupportedOperationException is thrown.

Block File Types

The BlockFile interface also includes a static inner class which defines an enumeration of the types of BlockFile. This is the standard enumeration pattern used before Java 1.5.

The defined types are:
MAPPED
The file is memory mapped. Reading and writing are implicit, and the read/write methods perform no function.
EXPLICIT
The file is accessed with the standard read/write mechanism.
AUTO
The file is accessed by memory mapping if the operating system supports a 64 bit address space. Otherwise file access is explicit.
DEFAULT
This is set to AUTO, unless the value is overwritten at the command line. The override is performed by defining tucana.xa.defaultIOType as mapped, explicit or auto.
The code for determining this setup is in the static initializer of the enumeration class.

This enumeration class is used by the factory methods in AbstractBlockFile to determine which concrete implementation should be instantiated.

Thursday, March 02, 2006

Posts

I've noticed that my new blog has slightly different options than my old one. For instance, the new blog has a "title" that I can fill in for every post, but this blog doesn't. I wonder why the difference, and if it's possible to upgrade?

Also, I've been using <h3> tags in the new blog, to match the template I'm using there. I notice that <strong> doesn't do a lot in the current stylesheet for this blog, so I decided to trial using <h3> here as well.

MappedIntFile

The natural approach to mapping a file is to simply map the entire file from beginning to end. Any accesses to this file become simple get() and put() operations, which in turn get passed into direct memory accesses.

In Java this suffers from 2 major problems. The first is the already mentioned issue of the 2GB limit, even for 64 bit operating systems.

The second problem is the inherent difficulties in remapping the file when it grows, since it is impossible to guarantee unmapping of a file. A new mapping plus the old one may have a combined total that is larger than the available address space, so it is impossible to have both in existence at once. Another issue, is that remapping a file that has grown is a very expensive operation, particularly when the garbage collector will be repeatedly run in an attempt to clean up older file mappings.

A way to avoid all of these issues is to break the mapping into a series of smaller consecutive mappings:
  • Even though the 2GB limit exists for a single mapping, a 64 bit system may map up to hundred of GB of files.
  • Expanding a file just means a new mapping goes onto the end of the file. For this reason, files get expanded in "chunks". This means that the old mappings hang around and don't conflict with the new mapping, and not crowding out memory.
  • Not cleaning up the old mappings means that there is no overhead in cleaning up the mappings, particularly the expensive garbage collecting.
These mappings are all managed in an array of MappedByteBuffer called (funnily enough) mappedByteBuffers. Enabling access to the same data in ints or longs is a pair of associated arrays labeled intBuffers and longBuffers.

The use of these arrays of buffers makes everything more complex in this file, but not excessively so. The constructor has to calculate the number of buffers needed to cover the entire file, and sets up the arrays to hold them. It then passes the work on to a method called mapFile(). This method loops through the array of buffers, and for each element it sets up the mappings to the appropriate areas of the file. This method is also called when a file is expanded, in which case it copies all the existing mappings from the original array, and appends the new mappings to the end of the new array. Any failed attempts at mapping will result in the garbage collector being run, and the mapping attempted once more. This process is in a loop that will run 10 times (yes, that's a magic number that seems to work well. It should probably be configurable).

A mapped file can be expanded or contracted by calling setSize(). There are currently no use cases to shrink a file, except for cases where the file is shrunk to zero size (with clear()), so smaller sizes than the current size are ignored. Once the size is determined, mapFile() is called again to update the mapping arrays and create the new mappings.

Each of the get and put methods first need to determine the correct mapping to use (by taking the absolute offset divided by the size of the mappings) and the offset into the mapping (by taking the absolute offset mod the size of the mappings). Does this remind any Intel "protected mode" programmers of selectors and offsets? :-)

Once the correct offset within the specified mapping is found, then the access goes ahead as normal.

Some of this may sound like significant overhead, but it is not. The majority of the extra work is simple integer arithmetic, which will be performed in parallel to other operations on most modern processors.

The result is a MappedIntFile which can be expanded while mapping a large portion of memory, can handle more than 2GB, and is as fast or faster on all operations than a single large mapping would have been.

Sunday, February 26, 2006

Grammar
The week was filled with lots of extra-curricula stuff, mostly (though not entirely) due to a visit from the client whose project I'm working on. But by the end of the week I finally had the grammar code parsing a lot of straight forward English into RDF. I also have a few "hacks" to make it a little more RDF friendly, such as picking up prepositions in predicates.

For instance, when I first parsed, "The quick brown fox jumps over the lazy dog." I was getting a subject of fox a predicate of jumps, and an object of dog. This says a something a bit different to the original intent. By picking up the preposition for the adverbial phrase, I instead get a predicate of jumps over, which is what I wanted.

It's still rudimentary, but it's very cool to stick in natural English and get out sensible RDF. I'd love to open source this stuff, but I did it on the company's dime, so it's their call. Probably not, but they want to get more involved with OSS, so maybe.

On the other hand, it wouldn't hurt if the code were never open sourced. I didn't really know what I was doing when hacking the grammar code, so it could be a lot prettier (and more extensible). Releasing dirty code into the wild can just be a recipe for embarrassment. :-)

Advanced Degrees
DavidW posted his result on the What Advanced Degree Should You Get test. OK, so these tests are far from giving a definitive portrayal, but I couldn't fight the temptation to fill it in. What do you know? I got:

You Should Get a PhD in Science (like chemistry, math, or engineering)

You're both smart and innovative when it comes to ideas.
Maybe you'll find a cure for cancer - or develop the latest underground drug.

Not really surprising. Maybe it's telling me to hurry up with this OWL/RDF thing and get back to my Physics postgraduate study (I never intended to be away from it for this long).

Personal
Anne has been keeping a blog about our recent move to Chicago. I've had some of my own thoughts and opinions since getting here, so I finally decided to keep a personal blog about it.

It won't be interesting to anyone who doesn't know me, but I thought I'd mention it in case any of my family ever read these posts. :-)

(Besides, an extra link never hurt a Google ranking. Oh wait... it did).

Tuesday, February 21, 2006

Chicago PD
I was going to complain cathartically tonight, but Anne's done it for me. I've started going to the gym again tonight, so that's helped me feel a bit better, despite my lack of catharsis.

We have a chain on the door now, so hopefully we'll have no more incidents. I'm still feeling paranoid though.

Bottom Up
There is a lot to be said for describing a system from the top down. The overall structure comes into view very quickly, and there is a concentration on the general architectural concepts which are so important for understanding and modifying the code.

However, I have found that describing Kowari at a high level always seems to lead to questions about the details. There are concepts that people seem to struggle with until they see how all the details are managed. This encourages me to approach the system from the ground up. An obvious advantage of this approach is that there are no dependencies on unexplained systems. Conversely, a lot of ground has to be covered before the overall structure comes into view.

There are also a number of operations at the bottom levels which are created to support higher level concepts. While the operations are easy to follow, the need for them may be unclear until the higher level code is viewed.

Possibly the best compromise is to start at the top, explain the requisite lower-level details as needed, and to return to the top whenever possible. My main problem here is that the theme of the discussion jumps around a lot, I may also end up discussing details which people didn't need, while skipping those which were more interesting.

In the end I've decided to go bottom-up. At least there I can be consistent with this approach, and I don't have to be concerned about missing details. More importantly, once I start getting higher up in the architecture, it will be possible to refer back and forth as required (that's an advantage of written explanations over spoken ones). The one piece of architecture that I feel is important to know at this level is the Phase Tree design. This was covered in my last entry.

The descriptions I'll provide for the lower level classes should be considered a supplement to the Javadoc. If anyone would like to see more info on anything, then please let me know.

I'll start in the org.kowari.util package.

IntFile
This class provides the semantics of an array of 64 bit long values, backed by a file. It has operations to set the size of the array (setSize(int)), and to set and get values from any position from 0 to the end of the array (putLong(long, long) and getLong(long)).

A less used set of options, are the getters and setters which can be used to treat the structure as an array of 32 bit int values (putInt(long, int) and getInt(long)), or 8 bit byte values(putByte(long, byte) and getByte(long)).

This class also provides storage for an array of "unsigned" 32 bit integers. This is handy for file access, and managing some data types. However, unsigned values are not directly supported in Java (which the exception of the char type). To manage them, they are passed around in 64 bit longs so they get handled correctly, but they are still stored on disk in a 32 bit pattern. The methods here are called putUInt(long, long) and getUInt(long).

Concrete Classes
IntFile is an abstract class, and implements minimal functionality. The real work is done in a pair of concrete classes called ExplicitIntFile and MappedIntFile.

MappedIntFile uses the New IO (NIO) functionality introduced in Java 1.4. It memory maps a file, allowing the underlying operating system to manage reading and writing of the required memory buffers. This leverages the efficient management of disk and memory that is essential in modern operating systems, and avoids the traditional copy to or from additional memory buffers.

Unfortunately, memory mapping files in this way is restricted by the size of the address space of the process. On a 32 bit system, this creates a maximum limit of 4GB, though the program and the operating system must use some of this space. 64 bit systems don't do much better, as many 64 bit operating systems impose limits within their address space for various operational reasons. In Java, the addresses for NIO are all 32 bit int values. Since this is a signed value (the sign using up one bit), the maximum size for a single file mapping is 2GB.

All this means that if a Kowari instance needs more space, it has to revert to using standard read/write operations for accessing the files. This is the purpose of the ExplicitIntFile class. This class implementation of each of the put/get methods calls down to read/write methods on the underlying java.nio.channels.FileChannel object. This implementation is not as fast as MappedIntFile, but it can operate on much larger files, particularly when address space is at a premium.

More IntFile
The constructors for each of the concrete implementations of IntFile are all default (not public). Instead, the IntFile class contains factory methods which instantiate the appropriate concrete class according to requirements.

The factory methods are all called open(...), and accept either a String or a java.io.File object. They will open an existing file when one exists, or create a new file otherwise.

First of all, the system properties are checked for a value called "tucana.xa.forceIOType". If it doesn't exist, then it defaults to using MappedIntFile. Otherwise, it makes a choice based on the expected values of "mapped" or "explicit". Any other value will fall back to MappedIntFile and give a warning in the log.

Unfortunately, we have found a bug that occasionally manifests in ExplicitIntFile. DavidM tracked it down, but it is tough to reproduce. I don't have the details, but on hearing this I audited this class, and it all appears correct. Until we have a solution to this problem, the factory method is temporarily using MappedIntFile in all cases (this has been a problem for some users, and needs fixing).

Forcing, Clearing, and Deleting
The remaining methods on IntFile are for managing the file, rather than the data in it.

force() is used to ensure that any data written in the putXXX(long, XXX) operations has been written to disk. This relies on the operating system for the guarantee of completeness. This is relevant to both mapped and explicit IO, as both are affected by write-behind caching. This operation is essential for data integrity, when the system needs to know that all files have reached some fixed state.

clear() leaves the file open, but truncates it to zero length, thereby deleting the entire contents of the array.

close() simply releases the resources used for managing the file, and is primarily used during shutdown of the system. delete() also releases all the resources, but then deletes the file as well.

The final method to consider is unmap(). This method is only relevant to the MappedIntFile class, but it must be called regardless of the implementing class used. This is because any calling code cannot know which implementation of IntFile is being used (this is intentionally hidden, so it can be swapped with ease).

When unmap()is called on MappedIntFile, all the references used for the mapping are explicitly set to null. This allows the Java garbage collector to find the mappings and remove them, thereby freeing up address space.

This is an important operation, but it is difficult to enforce. Java does not permit explicit unmapping of files, since allowing it would permit access to memory which is not allocated (a General Protection Fault on Windows, a segfault on x86 Linux, and a bus error on Sparc Linux and Mac OS X). The closest we can come to forcing this behavior is to make the mapping available to be garbage collected, and run the garbage collector several times until the mapping has been cleaned up. This actually works on most operating systems, but needs to be iterated much more on Windows before it works.

Late Night
There are some specific details in MappedIntFile that need to be addressed, but I'll have to leave here and get some sleep. Hopefully I won't be woken by the Police tonight...

Thursday, February 16, 2006

Expenses
No blogging last night, as I was working on expense reports. Somehow I doubt that work will appreciate that I had to stay up to 1am, but they'd certainly notice if I hadn't. Even so, I had to spend more time on them this afternoon.

As if that weren't enough, I've also filled in all the paperwork required to become a full-time employee. It's a little scary when you have no idea of the tax system in this country. It also doesn't help that I don't have an SSN.

I applied for an SSN last week (my first day in town), but they told me that USCIS (formerly the INS) have not yet entered our names into the system. Apparently that takes 10 working days. Getting an SSN after that will take another 10 working days. So it will take me a month to get the one piece of ID needed to do practically ANYTHING in this country.

It seems a little weird that it's this hard, given that every person who is given a work visa here will require an SSN. So why not process it all together and hand out the SSN with the visa? Not to mention that some official documents require a Resident Alien number, but I have to apply for that separately as well! I'm not sure where to get it either. Possibly USCIS.

In the meantime, Anne can't get an SSN because she's not entitled to work. This is despite the fact that her visa does allow her to work! It turns out that she has to go back to USCIS and apply for permission to work. Once she has that she can apply for her own SSN.

I'd hate to know what will happen to us if we have to talk to a doctor. We're insured, but won't they ask for an SSN?

Bus Error
It turns out that I was a little sloppy with some recent JNI code. If my Dictionary class failed to load in C, then it returned a null, as documented. However, I blithely stored this pointer and went on to use it.

Until now the Dictionary class always loaded, because I always made sure the dictionary files were available. But yesterday I started running a new set of tests in a separate directory, and they immediately reported: bus error.

I forgot how undescriptive a fault in C can be. :-)

It shouldn't have taken long to find this, but I used Eclipse to trace where the fault was occurring. While the GUI is slower on a Mac, for some reason the whole system brought my system to its knees. The trackpad was so jerky and intermittent that I started to worry that it would need fixing. It was only after tolerating this for an hour that I realized that the problem was not the trackpad, but that the machine was struggling too much. Shutting down Eclipse fixed the problem.

I'd be tempted to continue working from the command line, along with VIM, but Eclipse can't stand me fixing files outside of its domain. Why should it care?

At least I discovered the problem easily enough, and fixed it by bringing the dictionary files into the test area. I've also moved into the code and I now throw exceptions when a C library fails to initialize. I'm disappointed I hadn't done this in the first place, but at least I'll remember now (once bitten, twice shy).

BTW, has anyone else noticed that Intel (and x86 compatible) based systems report a segfault when dereferencing 0, while Apple and Sparc systems report a bus error? I always think of null dereferences as being segfaults, so I used to get confused when I saw bus errors like this. I wonder why there's a difference.

Tuesday, February 14, 2006

Oracle
After being aware of it for along time, today I finally had a brief look at Oracle's RDF support in 10g. I was really hoping that Oracle would bring their experience in developing database structures to the RDF domain, creating something very fast and scalable.

Unfortunately, it appears that 10g implements RDF as a schema in a set of standard relational tables, and have wrapped access to the system within Sesame interfaces, including SAIL. There's nothing wrong with building a Sesame system with an Oracle backend, rather than MySQL, but this isn't the Oracle system that I was hoping for. It doesn't bring in the extra efficiencies needed to make RDF really move. After all, RDF has a strict shape to the data, while a RDBMS needs to handle data of all kinds of shapes. This is why Kowari has such fast load times (when configured correctly).

Interestingly, the paper which describes the details of the system references Kowari. I was surprised at this when I read marketing phrases like:
Oracle Spatial10g release 2 introduces the industry's first open, scalable, secure and reliable data management platform for RDF-based applications.

Kowari was open, scalable, and reliable (to the best of my knowledge), and TKS was secure (one of the reasons for buying the commercial system over the Open Source Kowari).

Another quote says:
A key feature of RDF storage in Oracle is that nodes are stored only once -– regardless of the number of times they participate in triples.

The wording here suggests that Oracle is unusual in this regard, but almost all the RDF systems I am aware of share this feature, including Kowari. Perhaps the multiple indexing in Kowari caused some confusion here?

It appears that RDF support by Oracle has been implemented at the highest layers by RDBMS programmers. While it undoubtedly works, I'm disappointed that they haven't implemented RDF at a lower level. Still, it would be interesting to see how many triples a second it can load.

Free Lists
Now that I've discussed the principle behind phases, the next step is to discuss the classes which support this process. At the lowest level this is the class called FreeList. This class manages resources which are allocated and/or released in various phases. It enables new resources to be created as needed, and freed resources to be re-used efficiently.

The kinds of resources managed by the FreeList class are all the fixed-length records within data files, and also the numeric identifiers used for RDF nodes. The name of the class is an historical holdover from when it was simply used to hold a list of items which had been allocated and then freed. It does a lot more now.

FreeList sits over the top of two other classes called BlockFile and IntFile, both of which are relatively easy to describe. Unfortunately, FreeList itself will take me some time to write about, and a late night is not the time to start. So I'll get into it in my next entry.

Monday, February 13, 2006

Files
With work and the move keeping me busy it's been a while since I last looked inside Kowari. To kind of ease myself back into it, I thought I should finally get around to writing some notes on the Kowari file system.

Phases
At all levels of abstraction inside the Kowari file system, the concept of phases keeps appearing. Phases are a way to keep track of a constant state in the system, regardless of any write activity that may be occurring. They are also used to serialize write access from different connections. While this does enforce consistency in the data on disk, the inability to allow multiple parallel writers is a current weakness in Kowari. (This is the principle reason the XA2 file system has been proposed).

Phases were borrowed from the TUX2 filesystem. In this approach, all data is laid out in a tree, with pointers going down from parent nodes to child nodes. This is a common pattern for data storage. (When I get OmniGraffle working again I'll put in a simple diagram of a tree here).

When a change needs to happen to a node in the tree, then that node is first copied to one side. The new copy contains pointers to the same children as the original node. Then each ancestor of this node is copied recursively, all the way up to the root of the tree. Each new parent node will refer to the copied child, but all the other children will be the same as the original node. The result is a pair of roots to two trees which are indistinguishable from each other, and which share many of the same nodes. (This is where I really need the diagram).

At this point, the new nodes can be modified without affecting the old tree. If modifications are required on any nodes in the old tree, these nodes can also be copied, along with all the parents up to an already copied parent. As soon as a node is found that has already been copied, then the existing copy can be adjusted to point to the new child node.

Once the write operation to the tree is complete, the old root will refer to the tree before the write, while the new root will refer to the tree after the write. The old tree was never touched, so it is guaranteed to be completely consistent. Once the new tree has been completely written, and is in a consistent state, then both trees will be completely consistent. That means that it is safe to use either root of the tree, without fear of power blackouts, OS crashes, etc.

At this point, a single number which holds the address for the current root of the tree can be updated. This can be performed atomically on any hard drive. Even if the power fails, every hard drive available today has enough capacitance to complete the write operation of a single block.

To guarantee an atomic write like this (ie. to guarantee a consistent tree on disk) the host operating system has to allow a force operation to disk. This is supported in Java, which in turn relies on the file system API of the underlying operating system. It is concerning that some operating systems have been known to postpone "force" operations (eg. Mac OSX), but this is a matter that is out of the control of user level code. Fortunately, the conditions required for this to cause a problem are far less likely to occur than the likelihood of hardware failure, so the risk is manageable.

Note also that the new root of the tree can be abandoned at any time with no consequences for the original tree. All that is required then is the ability to clean up or recover all the copied nodes created for the abandoned tree.

A new root to a tree as described here is called a new phase. Selecting a phase to be the new primary root of the tree, is known as committing the phase. Committed phases are always in a consistent state, removing the need to journal modifications.

Another feature of phases is that at any point in time a phase may be "kept" for reading. Any future modifications will start up a new phase (creating a new root to the tree). This means that it is possible to keep multiple phases active at any time, with each phase representing a "snapshot" of the data in the tree at the time that phase was kept. This is important for multiple readers to be able to get the latest available data while a long write operation is continuing. To ensure that the snapshots are kept consistent, they must not be modified. This means that only the most recent phase may be written to.

This describes Kowari's inability to allow multiple writers. However, this strategy is done to ensure that Kowari has a completely robust filesystem that is efficiently capable of withstanding any error, including the sudden removal of power. While speed and robustness are important, enterprise systems also require multiple concurrent writers. This has led to the plans for XA2.

XA2 will allow for multiple writers, but this in turn will require a form of journaling. Journaling is less efficient than phase trees, but we expect to gain in other areas of efficiency, making the overall performance even better. The system is more complex than the phase trees discussed here, so I will leave discussion of this for another time.

Thursday, February 09, 2006

Landed
I resisted the title "Sweet Home Chicago", as I'm sure everyone who has ever moved here has said that. :-)

We got through the flight successfully, though we didn't get much sleep. It's harder to sleep when looking after children who aren't sleeping much either. Luc was amazingly well behaved. It was only in the last couple of hours that he started getting hyperactive (maniacal laughter, treating everything as a game, getting into everything, etc). Fortunately there were no tears or tantrums. Nic just slept.

Membership of the Qantas Club was wonderful at LAX. We got internet access (so I could confirm the flight time with JohnN at work), but more importantly, they had a room specifically for children. So we got a little private space, and Luc got to play with Tonka toys while watching Nickelodeon. Compared to sitting on a crowded concourse it was a little slice of heaven.

JohnN picked us up at O'Hare airport, and drove us into town. We had a lot of bags, so I'm glad we didn't have to arrange our own transport. I was also glad to have someone else driving, as it started snowing when we arrived. I've been in snow, but almost never seen it falling, so it was nice to see (though I'm sure the locals didn't enjoy it).

The apartment is minimally set up, which has been enough for the first day, but we need to get out and buy things like shelves if we want to stop living out of suitcases. We also need to buy some good cold-weather clothing for the boys, as they don't have enough. Frustratingly, most places have sold out, as they are bringing in the new season in preparation for Spring.

So yesterday and today are being spent shopping for essentials. I'm just grateful I don't have to deal with work, as I'm still suffering pretty badly from jet lag.

Wireless
The apartment came with cable access for the internet, but the company hadn't got around to brining over a wireless router. Fortunately, I had my AirPort Express with me, plus an extra US power adapter, so I plugged it in expecting it to all work.

While connecting the modem directly to my notebook worked fine, I couldn't make it work with the AirPort. The notebooks could connect to the AirPort just fine, but all routing stopped at that point, and there was no DNS.

I tried changing every setting I could think of, including setting the IP details statically to the details obtained when I plugged the notebook in directly. But the AirPort kept acting as if the ethernet was not there at all. I finally reset everything to what I thought it should have been (the settings I'd originally used, and then disconnected power to both the AirPort and the cable modem. 30 seconds later everything worked.

This isn't the first time I've seen certain devices fail connecting to a network, but not others. I wonder who was at fault in the protocol here... the AirPort or the cable modem?

Monday, February 06, 2006

Stress and Moving
This is probably my last entry from Australia for the time being. We've just spent the last few days in a mad scramble trying to finalize the things we thought were already done, or easier to complete than they really were. I hope we're near the end of it.

Tomorrow morning will see the moving company arrive, and take almost everything we own. We won't see any of it again for at least 8 weeks. We were told 8-14 weeks, so I'm expecting that it will really be around 16 weeks. Then off to a hotel, and onto a flight the next morning. It's a 12:10pm flight to LA... meaning that we have to check in no later than 9:10am! Sounds excessively early to me, but I suppose they need that to deal with the conditions set out at LAX.

In the meantime, we've been running all over town managing accounts, banks, and insurances. I've also had to spend a significant sum on travel insurance to cover us until my medical insurance becomes valid in April. This wasn't easy, as most providers won't handle people taking one-way trips. It had to be travel insurance, as we needed to be covered in case of problems on the trip, particularly if something happened to my notebook computer. Unfortunately, the only "one-way" insurance I found does not cover computers (only cameras), so I had to spend another couple of hundred to cover the computer as well. It all adds up, and I don't expect to see my credit card reimbursed soon. I'm going to be in a lot of debt for a while on this one.

Luc seems to be dealing with everything very well, and is still happy most of the time. I'm very grateful for this. He still needs his attention, so it can get difficult on occasion. Nic is as happy as ever. Anne and I are both very tired, and I'm looking forward to it all being over.

We've been told that we'll have internet access when we get there, so here's hoping I can get online quickly once we do.

Friday, February 03, 2006

Drowning
The visas finally arrived, and now I can do all the last minute things I have to get done before leaving Australia for good. The power will go off at the appointed time, as will gas, phone, internet, etc. Annoyingly, I can't cancel my mobile phone until I want it cut off, so I'll have to wait until the appointed time. I'll do that from overseas, as I'd like a bit of overlap so I can continue to make calls.

More frustrating is my health insurance. The Australian government gives a small subsidy to private health insurance but this is reduced if you spend any time after turning 30 without private cover. Because of this, I can only cancel my account without future penalties if I show my travel itinerary at an office. I have the itinerary now, but I'd rather not have to travel in.

My main problem at the moment is that I'm swamped with paperwork. Flights (obviously), visas (done, thank goodness), mail redirection (sorted), insurance (travel, landlords, shipping), mortgage (refinanced because the CBA would be a nightmare to deal with from the USA - they're bad enough in Australia), shipping forms, customs declarations, car sale, and rental documents. In the midst of this, our printer has decided to start mangling paper, so all those PDF documents I've been sent to fill in aren't helping.

Most of it takes a little time, but my "Supplemental Declaration for Unaccompanied Personal and Household Effects" form has one sticking point. It requires my "Resident Alien No." only I know nothing about this. My visa isn't forthcoming, and I have no other documents.

The visa has a 14 digit "Control Number" on it, but that seems to be a different number altogether. I found a document on the web (an application for info form at Sandia) which asks for both the Visa Control Number and the Resident Alien No. There is also an 8 digit number in red on the visa, but it's completely unlabelled, so I don't know what it might be. Google was surprisingly unhelpful in this regard. The best I've been able to do os to find the same form provided by other shipping companies, with all of them saying that the questions should be "self-explanatory". Great.

While writing some of this, my brother has come online and has started explaining some of it. I knew that I have to apply for an SSN once I get there (why not make it a part of the visa process?), but I didn't know that I also need to get a "permanent residence card". He also told me that he didn't get his "Resident Alien Number" until he'd been living there for a few months.

I'm starting to get confused about how I can actually move to this country. Is it possible?

Work
Unsurprisingly, work has slowed right down now, though I'm still trying to keep my hand in.

Someone had a problem the other day about using decision trees. They weren't scaling all that well (they scale by log(n) obviously), and he was trying to pick up some scalability from somewhere. He thought of breaking the tree into several subtrees, which might make the structure more manageable, but still requires the same number of decisions.

I started thinking about the first decision, which determines the sub-tree to go to. It occurred to me that several layers of decisions could be merged into a single "hashcode", which then finds the tree through a hash map. This tries to trade time for memory (less time for decisions, but more memory in the hash table). It's possible to go this route, but it requires careful merging of the data into the hashcode. If each of the elements going into the code require a test of some sort, then the number of tests to find the final data will not change. It's one of those things where you really need to see the shape of the data involved to see whether or not a particular optimization will work.

Merging data into a single "hash" is kind of like representing each of the elements of the data as separate dimensions. The hash code then leads to a point in N-dimensional (N-D) space representing the data you have so far. Using the hashcode to find the decision tree to use, means that these trees represent the work to be done for various regions of N-D space. This then brought me back to neural networks, as this is similar to how they are modeled.

This made me realize that in some ways a hash table can act like a neural network. The main difference here is that neural networks are forgiving for unexpected data. Hashtables can only be made to work this way if they can cover all allowable points in space. If the space isn't sparse, then that means that the hashtable might as well be an array - just so long as there is a way to map co-ordinates to something.

Anyway, it has me thinking that I might have to pull out some neural network algorithms, dust them off, and have a go at applying them to some of our problems in document categorization. I haven't used them in years, so it will be a real blast of retro for me.

RDF
In the meantime, it's about time that I look at RDF again. I still plan to use Kowari (since it's under the MPL, and that can't be changed), but while NGC are making things awkward I've been considering other routes. I'm tempted to use Sesame for a while, to learn more about it if nothing else. However, I'm not sure that I can make my thesis work in this framework, so I still need to look at other options.

I've been thinking of having a go at DavidM's skiplist code, to see if I can use it as the core to a new storage layer. If that works out, then I can start building some of the other layers on top, avoiding the problems that have had to stay in Kowari for historical reasons. Many of the top layers in Kowari were developed (as open source software) in the last year, so these can be transplanted easily.

I don't pretend that this would turn into a fully fledged RDF store (at least, not without a lot of time and help), but it could be a useful exercise. It would be enough for what I want to do OWL inferencing anyway. It might also go a bit quicker than Kowari, since I've already done a lot of this stuff once before! :-)

I'd rather work with Kowari (for the time being), but at least this way I don't have to worry about NGC interfering. It also starts introducing the skip list code that Kowari has needed for some time.

I'll see if I get any time for this in the coming week. It may be hard. I can't even do much on the plane, as we will be watching two young children. Still, it doesn't hurt to keep thinking these things over.