Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ Ways this differs from a scala requirments.
on is still safe and unchanged. This allows reads and writes to happen without blocking
eachother and without any locking. (persistant data structures are awesome)
* The nodes themselves are serialized and stored in a file. First the length of the serialized
node is written to the file (this is an int that serializes to 7 bytes) then the byte data of
node is written to the file (this is an int that serializes to 14 bytes) then the byte data of
the node is written. The location in the file is returned (and stored in other nodes as pointers)
When a node is read from the file first 7 bytes are read from the location to get the size and then
When a node is read from the file first 14 bytes are read from the location to get the size and then
that many bytes are read and deserialized to get the node. The reader and the writer each use a different
file object so the random access jumping around of reads does not hinder the high throughput that linear
writes to the end of the file has.
Expand Down
10 changes: 5 additions & 5 deletions project.clj
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
(defproject steveskeys "0.1.0"
(defproject steveskeys "0.2.0"
:description "file backed key value store"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.4.0"]
[org.clojure/math.numeric-tower "0.0.1"]
[com.google.guava/guava "10.0.1"]
[com.taoensso/nippy "1.0.0"]])
:dependencies [[org.clojure/clojure "1.6.0"]
[org.clojure/math.numeric-tower "0.0.4"]
[com.google.guava/guava "18.0"]
[com.taoensso/nippy "2.8.0"]])
14 changes: 7 additions & 7 deletions src/saolsen/steveskeys.clj
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
(defrecord DiskStore [fs keys vals]
PDiskStore
(put! [_ key value]
(let [k (n/freeze-to-bytes key)
v (n/freeze-to-bytes value)]
(let [k (n/freeze key)
v (n/freeze value)]
(if-let [val (get @vals v)]
(swap! keys assoc k val)
(let [new-val (file/write-node fs v)]
Expand All @@ -24,21 +24,21 @@
true))

(get! [_ key option]
(let [k (n/freeze-to-bytes key)
(let [k (n/freeze key)
v (get @keys k)
val (file/read-node fs v)]
(n/thaw-from-bytes val)))
(n/thaw val)))

(flush! [_]
(file/commit fs (btree/get-root-loc @keys) (btree/get-root-loc @vals))
true)

(traverse [_ start end]
(let [kvps (btree/traverse @keys
(n/freeze-to-bytes start)
(n/freeze-to-bytes end))
(n/freeze start)
(n/freeze end))
get-kv (map #(vector (:key %) (file/read-node fs (:val %))) kvps)
to-vals (map #(map n/thaw-from-bytes %) get-kv)]
to-vals (map #(map n/thaw %) get-kv)]
(reduce #(conj %1 (first %2) (second %2)) [] to-vals)))
)

Expand Down
37 changes: 19 additions & 18 deletions src/saolsen/steveskeys/file.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
;;
;; {:keys pointer :vals pointer}
;;
;; This when serialized by nippy takes up 31 bytes so the first 62 bytes of the
;; This when serialized by nippy takes up 37 bytes so the first 62 bytes of the
;; file are the two headers. The second header is written to first. If the
;; program fails during this write the first header will still contain the last
;; flush's root. Then the first header is written to, if the program fails
Expand Down Expand Up @@ -38,14 +38,14 @@
(defn try-thaw
[bytes]
(try
(nippy/thaw-from-bytes bytes)
(nippy/thaw bytes)
(catch Exception e nil)))

(defn read-headers
"reads the two headers"
[f]
(let [b1 (byte-array 31)
b2 (byte-array 31)]
(let [b1 (byte-array 37)
b2 (byte-array 37)]
(doto f
(.seek 0)
(.read b1)
Expand All @@ -58,18 +58,19 @@
PFileManager
(read-node [_ pointer]
(.seek reader pointer)
(let [b1 (byte-array 7)]
(let [b1 (byte-array 14)]
(.read reader b1)
(let [size (nippy/thaw-from-bytes b1)
(let [size (nippy/thaw b1)
b2 (byte-array size)]
(.read reader b2)
(nippy/thaw-from-bytes b2))))
(nippy/thaw b2))))

(write-node [_ node]
(let [frozen (nippy/freeze-to-bytes node)
len (nippy/freeze-to-bytes (int (count frozen)))
(let [frozen (nippy/freeze node)
len (nippy/freeze (int (count frozen)))
tail (.length writer)]
(assert (= (count len) 7))
;(println "Efrain node:" node "frozen:" frozen "len:" len "tail:" tail "count:" (count len))
(assert (= (count len) 14))
(doto writer
(.seek tail)
(.write len)
Expand All @@ -78,10 +79,10 @@

(commit [_ keys-root vals-root]
(let [header {:keys (int keys-root) :vals (int vals-root)}
b (nippy/freeze-to-bytes header)]
(assert (= (count b) 31))
b (nippy/freeze header)]
(assert (= (count b) 37))
(.close reader)
(write-header-and-close writer 31 b)
(write-header-and-close writer 37 b)
(write-header-and-close (java.io.RandomAccessFile. filename "rws") 0 b)
(FileManager. filename
(java.io.RandomAccessFile. filename "rws")
Expand All @@ -95,18 +96,18 @@
head1
;; copy head1 to head2, return head1
(do
(.seek writer 31)
(.write writer (nippy/freeze-to-bytes head1))
(.seek writer 37)
(.write writer (nippy/freeze head1))
head1))
(if head2
;; copy head2 to head1, return head2
(do
(.seek writer 0)
(.write writer (nippy/freeze-to-bytes head2))
(.write writer (nippy/freeze head2))
head2)
;; database isn't initialized, write nil heads
(let [n (nippy/freeze-to-bytes {:keys (int 0) :vals (int 0)})]
(assert (= (count n) 31))
(let [n (nippy/freeze {:keys (int 0) :vals (int 0)})]
(assert (= (count n) 37))
(.seek writer 0)
(.write writer n)
(.write writer n)
Expand Down
42 changes: 21 additions & 21 deletions test/saolsen/steveskeys/btree_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@
;; Manually built tree for testing the search
(def test-nodes
{
:root (->BPlusTreeNode [{:key (nippy/freeze-to-bytes 2) :val :a}
{:key (nippy/freeze-to-bytes 5) :val :b}
{:key (nippy/freeze-to-bytes 9) :val :c}
:root (->BPlusTreeNode [{:key (nippy/freeze 2) :val :a}
{:key (nippy/freeze 5) :val :b}
{:key (nippy/freeze 9) :val :c}
{:key nil :val :d}])
:a (->BPlusTreeLeaf [{:key (nippy/freeze-to-bytes 1) :val "one"}
{:key (nippy/freeze-to-bytes 2) :val "two"}])
:b (->BPlusTreeLeaf [{:key (nippy/freeze-to-bytes 3) :val "three"}
{:key (nippy/freeze-to-bytes 4) :val "four"}
{:key (nippy/freeze-to-bytes 5) :val "five"}])
:c (->BPlusTreeLeaf [{:key (nippy/freeze-to-bytes 6) :val "six"}
{:key (nippy/freeze-to-bytes 7) :val "seven"}
{:key (nippy/freeze-to-bytes 8) :val "eight"}
{:key (nippy/freeze-to-bytes 9) :val "nine"}])
:d (->BPlusTreeLeaf [{:key (nippy/freeze-to-bytes 10) :val "ten"}])
:a (->BPlusTreeLeaf [{:key (nippy/freeze 1) :val "one"}
{:key (nippy/freeze 2) :val "two"}])
:b (->BPlusTreeLeaf [{:key (nippy/freeze 3) :val "three"}
{:key (nippy/freeze 4) :val "four"}
{:key (nippy/freeze 5) :val "five"}])
:c (->BPlusTreeLeaf [{:key (nippy/freeze 6) :val "six"}
{:key (nippy/freeze 7) :val "seven"}
{:key (nippy/freeze 8) :val "eight"}
{:key (nippy/freeze 9) :val "nine"}])
:d (->BPlusTreeLeaf [{:key (nippy/freeze 10) :val "ten"}])
:one "one"
:two "two"
:three "three"
Expand Down Expand Up @@ -50,27 +50,27 @@
isn't in the tree it returns nil"
[tree ks vs]
(doseq [[k v] (map #(vector %1 %2) ks vs)]
(let [result (get tree (nippy/freeze-to-bytes k))]
(let [result (get tree (nippy/freeze k))]
(is (= result v))))
(is (= (get tree (nippy/freeze-to-bytes 100)) nil)))
(is (= (get tree (nippy/freeze 100)) nil)))

(defn test-ranges
"checks that a bunch of ranges work, assumes ks and vs are the only things in
the tree"
[tree ks vs]
(dotimes [n 30]
(let [rks (map nippy/freeze-to-bytes ks)
(let [rks (map nippy/freeze ks)
len (count ks)
n (rand-int len)
m (+ (rand-int (- len n)) n)
keys (take (- (inc m) n) (drop n ks))
vals (take (- (inc m) n) (drop n vs))
kvps (map #(hash-map :key %1 :val %2) keys vals)
start (nippy/freeze-to-bytes (nth ks n))
end (nippy/freeze-to-bytes (nth ks m))
start (nippy/freeze (nth ks n))
end (nippy/freeze (nth ks m))
traversal (traverse tree start end)
checks (map #(and
(bequals (:key %1) (nippy/freeze-to-bytes (:key %2)))
(bequals (:key %1) (nippy/freeze (:key %2)))
(= (:val %1) (:val %2))) traversal kvps)]
(is (= (count kvps) (count traversal)))
(doseq [check checks]
Expand Down Expand Up @@ -99,14 +99,14 @@
id))
4)
s (flatten
(shuffle (map #(vector (nippy/freeze-to-bytes %1) %2) ks2 vs2)))
(shuffle (map #(vector (nippy/freeze %1) %2) ks2 vs2)))
added
(apply (partial assoc tree) s)
_ (test-they-all-exist added ks2 vs2)
_ (test-ranges added ks2 vs2)
;; test that after tree is made, duplicates can be replaced correctly
t (flatten
(shuffle (map #(vector (nippy/freeze-to-bytes %1) %2) ks vs)))
(shuffle (map #(vector (nippy/freeze %1) %2) ks vs)))
added-more (apply (partial assoc added) t)
]
(test-they-all-exist added-more ks vs))))
6 changes: 3 additions & 3 deletions test/saolsen/steveskeys/file_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
;; goodfirst has a root node of 0 as the first header and shit for the second
(deftest test-recovering
(testing "Making sure that it can recover from one of the header being bad")
(let [bad-buffer (byte-array 31 (map byte (range 10)))
fine-header (taoensso.nippy/freeze-to-bytes
(let [bad-buffer (byte-array 37 (map byte (range 10)))
fine-header (taoensso.nippy/freeze
{:keys (int 5) :vals (int 3)})
f1 (java.io.File. "testdata/goodfirst")
f2 (java.io.File. "testdata/goodsecond")
raf1 (java.io.RandomAccessFile. f1 "rw")
raf2 (java.io.RandomAccessFile. f2 "rw")]
(is (= (count fine-header) 31))
(is (= (count fine-header) 37))
(.createNewFile f1)
(.createNewFile f2)
(.seek raf1 0)
Expand Down