Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Designing a Clojure library for working with FoundationDB

Designing a Clojure library for working with FoundationDB

This talk introduces the reader to the basic concepts of FoundationDB and takes them through the journey of building a Clojure library - https://github.com/vedang/clj_fdb - for interacting with it.

Vedang Manerikar

July 03, 2021
Tweet

Other Decks in Programming

Transcript

  1. Designing a Clojure library for working with FoundationDB Vedang Manerikar

    <2021-07-03 Sat> Bangalore Clojure Meetup Group
  2. 1

  3. 2

  4. 3

  5. 4

  6. • The basic interface of a Key/Value store • ++

    performant range queries (because known ordering) • ++ performant transactions (ACID!) 6
  7. FDB has language bindings in Java, Python, Go (and some

    other languages as well, I think) 7
  8. Let’s write a Clojure wrapper! How hard can it be?

    • It’s a KV store. • get/set/clear • Question: What’s missing here? 8
  9. How does the Clojure world write data to databases? •

    Protocols (cutting edge: extend-via-metadata) • Custom Serialization (eg: nippy, transit) I looked at: • michaelklishin/monger • ptaoussanis/carmine • seancorfield/next-jdbc (and some others …) 12
  10. (with-open [db (cfdb/open fdb)] (fc/set db "blr-clj:20210703" "hello-world" :keyfn bs/to-byte-array

    :valfn bs/to-byte-array) ;; => nil (fc/get db "blr-clj:20210703" :keyfn bs/to-byte-array :valfn bs/to-string) ;; => "hello-world" (fc/clear db "blr-clj:20210703" :keyfn bs/to-byte-array)) ;; => nil 17
  11. ;; The Shape of the API (fc/set db k v)

    ; Key/Val must be byte-array (fc/get db k) ; Key must be byte-array, returns a byte-array (fc/clear db k) ; Key must be byte-array 18
  12. ;;; Examples of different Tuples (ftup/from "meetups") (ftup/from "meetups" "bangalore")

    (ftup/from "meetups" "pune") (ftup/from "meetups" "bangalore" "blr-clj") (ftup/from "meetups" "bangalore" "blr-py") (ftup/from "meetups" "pune" "pnq-clj") (ftup/from "meetups" "pune" "pnq-distsys") 20
  13. (fc/set db ["meetups"] []) ;; => key Tuple("meetup") stores value

    empty (fc/set db ["meetups" "bangalore"] []) (fc/set db ["meetups" "pune"] []) (fc/set db ["meetups" "bangalore" "blr-clj"] []) (fc/set db ["meetups" "bangalore" "blr-py"] []) (fc/set db ["meetups" "pune" "pnq-clj"] []) (fc/set db ["meetups" "pune" "pnq-distsys"] []) 21
  14. ;;; Querying ranges of Tuples (fc/get-range db ["meetups"]) ;; =>

    {["meetups" "bangalore"] [], ;; ["meetups" "pune"] [], ;; ["meetups" "bangalore" "blr-clj"] [] ;; ["meetups" "bangalore" "blr-py"] [] ;; ["meetups" "pune" "pnq-clj"] [] ;; ["meetups" "pune" "pnq-distsys"] []} ;; Similarly: (fc/get-range db ["meetups" "bangalore"]) ;; => {["meetups" "bangalore" "blr-clj"] [] ;; ["meetups" "bangalore" "blr-py"] []} (fc/get-range db ["meetups" "pune"]) ;; => {["meetups" "pune" "pnq-clj"] [] ;; ["meetups" "pune" "pnq-distsys"] []} 22
  15. ;; The Shape of the API (fc/get db k) ;

    Key must be byte-array / Tuple, returns byte-ar (fc/set db k v) ; Key/Val must be byte-array or Tuple (fc/clear db k) ; Key must be byte-array or Tuple (fc/get-range db k) ; Returns a map of all kvs under k (fc/clear-range db k) ; Clears all kvs under k 24
  16. ;; API evolution / support for Subspaces/Directories ;; somewhere else

    (def events-spc (fdir/create-or-open! db ["meetup" "events"])) (def members-spc (fdir/create-or-open! db ["meetup" "members"])) ;; in mark-attendance function (let [m-a (fc/encode members-spc [member-id "attends" event-id]) ev-a (fc/encode events-spc [event-id "attendance_counter"]) prev (to-int (fc/get db attendance))] (fc/set db m-a []) (fc/set db ev-a (to-byte-array (inc prev)))) 28
  17. ;; The Shape of the API (fc/get db (fc/encode s

    k)) (fc/set db (fc/encode s k) v) (fc/clear db (fc/encode s k)) (fc/get-range db (fc/encode s k)) (fc/clear-range db (fc/encode s k)) 29
  18. ;; Upcoming (fc/get-range db k), (fc/get-range db k opts) ;;

    opts contains: {:async? true :limit 10 ;; ... } ;; If opts takes up the 4th arity, having subspace in the ;; top-level becomes awkward. ;; Clojure's arglists metadata help here. (fc/get-range db k) (fc/get-range db k opts) ; These two clash, but okay (fc/get-range db s k) ; These two clash, but okay (fc/get-range db s k opts) 30
  19. ;; The Shape of the API: Latest (fc/get db k),

    (fc/get db s k) (fc/set db k v), (fc/set db s k v) (fc/clear db k), (fc/clear db s k) (fc/get-range db k), (fc/get-range db s k) (fc/clear-range db k), (fc/clear-range db s k) 31
  20. 33

  21. ;; We saw this bit of code previously ;; Updating

    it for the latest API (let [prev (to-int (fc/get db attendance))] (fc/set db members-spc [member-id "attends" event-id] []) (fc/set db events-spc [event-id "attendance_counter"] (to-byte-array (inc prev)))) 34
  22. ;;; Running the full code in a single transation: (ftr/run

    db (fn [tr] (let [prev (to-int (fc/get tr attendance))] (fc/set tr members-spc [member-id "attends" event-id] []) (fc/set tr events-spc [event-id "attendance_counter"] (to-byte-array (inc prev)))))) ;; Done. 35
  23. (defn mark-attendance [db member-id event-id] (let [prev (to-int (fc/get db

    attendance))] (fc/set db members-spc [member-id "attends" event-id] []) (fc/set db events-spc [event-id "attendance_counter"] (to-byte-array (inc prev))))) ;; ... somewhere in our code ... (ftr/run db (fn [tr] (mark-attendance tr member-id event-id))) 36
  24. (defn signup-student [db sid cid] (ftr/run db (fn [tr] (let

    [seats (to-int (fc/get tr cspc ["class" cid]))] (fc/set tr cspc ["attends" cid sid] []) (fc/set tr cspc ["attends" sid cid] []) (fc/set tr cspc ["class" cid] (to-byte-array (dec seats))))))) ;; (signup-student db "S1" "C10") => nil 38
  25. (defn drop-student [db sid cid] (ftr/run db (fn [tr] (let

    [seats (to-int (fc/get tr cspc ["class" cid]))] (fc/clear tr cspc ["attends" cid sid]) (fc/clear tr cspc ["attends" sid cid]) (fc/set tr cspc ["class" cid] (to-byte-array (inc seats))))))) ;; (drop-student db "S1" "C10") => nil 39
  26. (defn switch-classes [db sid old-cid new-cid] (ftr/run db (fn [tr]

    (drop-student tr sid old-cid) (signup-student tr sid new-cid)))) ;; (switch-student db "S1" "C10" "C11") => nil 40
  27. ;;; Different ways to run transactions ;; Blocking, returns the

    result of the function (ftr/run db (fn [tr])) ;; Blocking, only accepts reads on DB keys (ftr/read db (fn [tr])) ;; Non-blocking, immediately returns future (ftr/run-async! db (fn [tr])) ;; Non-blocking, immediately returns future (ftr/read-async! db (fn [tr])) 41
  28. ;;; The Shape of the API ;; Core [fc/get, fc/set,

    fc/clear, fc/get-range, fc/clear-range, fc/mutate!] ;; Transactions [ftr/run, ftr/read, ftr/run-async!, ftr/read-async!] ;; Directories, Subspaces, Tuples [fdir/create-or-open!, fdir/remove!, fdir/list fsub/create, fsub/range ftup/create, ftup/range] 43
  29. Features I need to add to the library • Watches:

    Unblocked after async • Versionstamps: Need to understand this better 44
  30. When choosing a new library • List down what tasks

    you expect to accomplish • Look at the API. How deep is it? 45
  31. When choosing a new library • List down what tasks

    you expect to accomplish • Look at the API. How deep is it? • Look at the API. How much magic is it? 45
  32. When choosing a new library • List down what tasks

    you expect to accomplish • Look at the API. How deep is it? • Look at the API. How much magic is it? • When you need it, can you drop down easily? 45
  33. A Big Thanks to Jan Rychter for discussing FDB with

    me and sharing his code! Talk to me about: 1. This Library: Use v0.3.0 and above • vedang/clj_fdb 2. Data modeling using FDB 3. Design patterns. 4. Working at Helpshift! mailto:[email protected] / @vedang on Twitter 46