Storage
Published: 2019-08-22 03:35:34

Part 5 in a series on how I'm building a database. [Part 4 was here](https://dev.to/quoll/mapping-the-way-15j2), and [the beginning was here](https://dev.to/quoll/what-s-with-data-structures-2fk7).

# Expanding
So far we've built a structure for representing linked lists and representing them in a raw buffer in Java. Buffers exist in Javascript too, and I do plan to demonstrate the same there eventually, but at this stage, I want to focus on what is being built more than how to build it.

The linked lists in previous examples were always of the same sequence of numbers: `1, 1, 2, 3, 5, 8, 13, 21, 34`. This list was also allocated in order, making the data much more regular than can be expected in most applications. So let's try something a bit different.

First of all, let's create a different sequence of numbers that shares the last 4 from the first list: `5, 6, 7, 8, 13, 21, 34`.
Then, let's create an unrelated third list: `3, 1, 4, 1, 5, 9, 2, 6`.

Because we are moving around a bit inside the buffer, then this shows that I had a bug in the existing implementation. Oops. 😳

(Yes, I can go back to my earlier post and fix the bug, but this is a blog, not a book! I'm going to admit my mistake).

The original definition of `BufferList` always takes a slice by setting the position, and then the limit. This worked while we were only moving the _position_ to the next space beyond the last _limit_ used, but now that it's jumping around a bit it shows that we were violating [the buffer invariants](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/nio/Buffer.html) that the _limit_ always will be at, or above, the _position_.

To fix this, we just need to see if the new position will be above or below the current limit. If it's above, then the limit can be set first, otherwise, the position can be set first:
```java
Element(int index) {
  this.index = index;
  int offset = index * ELEMENT_SIZE;
  if (offset > buffer.limit()) {
    intBuffer = buffer.limit(offset + ELEMENT_SIZE).position(offset).slice().asIntBuffer();
  } else {
    intBuffer = buffer.position(offset).limit(offset + ELEMENT_SIZE).slice().asIntBuffer();
  }
}
```
Incidentally, this code only works in Java 9 and above. Before that, the abstract `Buffer` class did not support `slice()`, although `ByteBuffer` did. So on Java 8 and earlier, the following modification would be needed:
```java
ByteBuffer bb;
if (offset > buffer.limit()) {
  bb = (ByteBuffer)buffer.limit(offset + ELEMENT_SIZE).position(offset);
} else {
  bb = (ByteBuffer)buffer.position(offset).limit(offset + ELEMENT_SIZE);
}
intBuffer = bb.slice().asIntBuffer();
```
My apologies if you tried my buffer slices from the last post on Java 8 and they didn't work!

## Building the Lists
Now that this is done, we can build our new lists. Of course, we will need a bigger buffer to store all of this, so we'll increase it to 20.
```java
BufferList bufferList = new BufferList(20);
BufferList.Element list1, list2, list3;

list1 = bufferList.addToHead(34);
list1 = bufferList.addToHead(list1, 21);
list1 = bufferList.addToHead(list1, 13);
list2 = bufferList.addToHead(list1, 8);  // note that this is list2
list1 = bufferList.addToHead(list2, 5);
list1 = bufferList.addToHead(list1, 3);
list1 = bufferList.addToHead(list1, 2);
list1 = bufferList.addToHead(list1, 1);
list1 = bufferList.addToHead(list1, 1);

list2 = bufferList.addToHead(list2, 7);
list2 = bufferList.addToHead(list2, 6);
list2 = bufferList.addToHead(list2, 5);

list3 = bufferList.addToHead(6);
list3 = bufferList.addToHead(list3, 2);
list3 = bufferList.addToHead(list3, 9);
list3 = bufferList.addToHead(list3, 5);
list3 = bufferList.addToHead(list3, 1);
list3 = bufferList.addToHead(list3, 4);
list3 = bufferList.addToHead(list3, 1);
list3 = bufferList.addToHead(list3, 3);
```
When adding the number `8` to the first list, we keep hold of that point in the list, and re-use it in the second list. This means that both lists share a tail. If the tail changed, then both lists would see the change. I'll return to this idea of shared structures when I get to Functional Data Structures.

Having 3 lists, we should be able to see the contents of all 3:
```java
System.out.println(list1);
System.out.println(list2);
System.out.println(list3);
```
The output is:
```bash
$ java buffer.MultiExample
1, 1, 2, 3, 5, 8, 13, 21, 34
5, 6, 7, 8, 13, 21, 34
3, 1, 4, 1, 5, 9, 2, 6
```
# File I/O
At this point, the structure can be stored durably by storing the buffer somewhere. The only things missing are the starts of the lists, which are associated with the elements `list1`, `list2` and `list3`. This data will also need to be stored, but since our buffer only holds elements, they will be stored close by rather than inside the list data structure.

## Metadata Read/Write
All the metadata about the buffer that we need to reconstruct the lists is found in the index values for the starts of the lists. We can write that to a file with a small method:
```java
static void writeLists(BufferList.Element... lists) throws IOException {
  RandomAccessFile file = new RandomAccessFile("meta.bin", "rw");
  for (BufferList.Element element: lists) {
    file.writeInt(element.getIndex());
  }
  file.close();
}
```
This creates the file in read/write mode, then goes through all of the elements given to it and writes their index values to the file.

Reading this back means reading those index values, and then asking the buffer to create the associated elements:
```java
static BufferList.Element[] readLists(BufferList bufferList) throws IOException {
  RandomAccessFile file = new RandomAccessFile("meta.bin", "r");
  int length = (int)(file.length() / Integer.BYTES);
  BufferList.Element[] lists = new BufferList.Element[length];
  for (int number = 0; number < length; number++) {
    lists[number] = bufferList.new Element(file.readInt());
  }
  file.close();
  return lists;
}
```
This opens the file for reading, then figures out how many integers are in the file based on its size. This gives the size of the array to return, and a loop reads each of the integers in the file and creates the associated `Elements` to return.

### Side Note
Both of these methods are illustrative of how some metadata might be stored, though for real-world applications we can expect something a little more tailored to the actual data structures in use. Typically, this metadata will be attached to the `Buffer` class that the metadata is representing so that reading/writing it does not appear as a separate step in the API. But I think it helps to see it all happening here.

## Buffer Read/Write
The `BufferList` itself only needs to write or read its buffer as a single block.
```java
public class BufferList {
...
  public void write(String filename) throws IOException {
    RandomAccessFile file = new RandomAccessFile(filename, "rw");
    buffer.position(0).limit(nextAvailable * ELEMENT_SIZE);
    file.getChannel().write(buffer);
    file.close();
  }

  public static BufferList read(String filename) throws IOException {
    RandomAccessFile file = new RandomAccessFile(filename, "r");
    ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());
    file.getChannel().read(byteBuffer);
    file.close();
    return new BufferList(byteBuffer);
  }
}
```
Note that the only part of the buffer that is written is between the current position and the limit, which is why these need to be set in the `write` method.

## The Other Side
With all this read/write in place we can update the main program to write the buffer and its metadata:
```java
public class MultiExample {
  public static void main(String[] args) throws IOException {
    BufferList bufferList = new BufferList(20);
    BufferList.Element list1, list2, list3;

    // ... populate the lists, as above ...

    System.out.println(list1);
    System.out.println(list2);
    System.out.println(list3);
    bufferList.write("buffer.bin");
    writeLists(list1, list2, list3);
  }
}
```
With this main program writing the buffer, can we reload it later? Let's write a second program to try:
```java
public class MultiExample2 {
  public static void main(String[] args) throws IOException {
    BufferList bufferList = BufferList.read("buffer.bin");
    for (BufferList.Element list: readLists(bufferList)) {
      System.out.println(list);
    }
  }
}
```
This is a totally fresh process that opens the file, and prints the following:
```bash
$ java buffer.MultiExample2
1, 1, 2, 3, 5, 8, 13, 21, 34
5, 6, 7, 8, 13, 21, 34
3, 1, 4, 1, 5, 9, 2, 6
```
Note how very little code is needed to load everything? This comes from the flexibility of the `Element` objects overlaying the buffer.

The complete source code for this can be found [here on Github](https://gist.github.com/quoll/192efb8060fcb47bcdca2912f1ca66bc).

## Discussion
As noted above, adding the metadata to the class definition removes the need for the client to manually process the other file, and also avoids having the client code having a direct reference to the `Element` constructor. This blog is still building a basic framework so there is no need to clean everything up just yet.

Javascript has a similar object to buffers in the [TypedArrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray). These objects can then be moved around similarly to a Java `Buffer`, and may be stringified for storage/retrieval from local storage, which lets us take this technique across to that system as well.

Unfortunately, the code as written here does not scale particularly well, as it requires the entire buffer to be written or read at once. Instead, we want a mechanism that can read or write individual buffers at a particular file offset. The read can be done during the `Element` constructor, and the write can be done when the element is finished with. In real-world systems, we will often put a _dirty_ flag on the elements and queue these modified elements for writing whenever they get updated. Modifications to an element are easy to catch since any modification has already been defined via setters and getters in order to modify the underlying buffer.

To manage this in Javascript, storing a `TypedArray` in local storage for each element would be a mistake, as there would be a large number of elements, leading to an excessive number of local storage entries. Instead, `TypedArray` objects could chunk up groups of elements, and then provide an API for accessing individual elements. For instance, when requesting element index 1234, with a grouping of 1000 elements, the group number and element offset within the group would be calculated as:
```javascript
groupSize = 1000;
group = Math.floor(1234 / groupSize);
offset = 1234 % groupSize;
```
Java's needs are different, as it can read/write any part of a buffer, from a single element to the entire buffer at once. However, an entire file should be represented by a single Java buffer, since a large file would result in a large amount of memory being used to mirror it.

This takes me to the next step of local disk storage: [Part 6 - Memory Mapping](https://dev.to/quoll/mapping-the-way-3fn).