Skip to content

philoskim/datomic-intro

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Datomic: 함수형 데이터베이스

1. 강사 소개

2. Datomic 소개

  • Clojure 언어의 창시자 Rich Hickey가 만든 함수형 데이터베이스

  • Clojure 언어로 쓰여져 있다.

  • "Immutable" Database: 개념상 git과 유사

  • 2012년에 최초 버전(0.8.3335) 발표

  • 현재 최신 버전: 0.9.5561.62

2.1. 참고: DataScript

  • Immutable memory database and Datalog query engine for Clojure, ClojureScript and JavaScript

  • 서버에서 사용되는 Datomic을 모방해 만들었다.

  • 주로 Web Browser Client용으로 사용된다.

  • Datomic과 거의 동일한 API를 제공한다.

4. Datomic의 특징

  • Transactional store, Analytics store, Distributed database, Graph database, Logic database

  • 관계형 데이터베이스 사용 예의 96% 대체 가능. 나머지 4%는 쓰기 작업이 많은(high write-volume) 경우.

  • 응답 속도가 빠르다 (거의 memory DB 수준)

    • ex) 1.5 ms per query, compared to the 10-20 ms in SQL query

  • Storage backend로 다양한 DB를 사용할 수 있다.

    • DynamoDB(AWS), Cassandra, Riak, Couchbase, Infinispan, or SQL database등

5. Datomic architecture

  • Reads are separated from writes and decentralized.

  • Writes don’t block reads, reads don’t block one another.

  • Read scaling horizontally by adding more "peer" systems.

datomic architecture
Datomic DB vs. SQL DB
   Datomic DB               SQL DB
=============================================
|-- Storage
|
|-- Transactor
|   |-- Writing
|   `-- Indexing          SQL Server
|
`-- Peer Library
    |-- Live index
    |-- Cache
====|========================================
    `-- Query             SQL Client Library
=============================================

6. Keyword data type in Clojure

6.1. keyword data type 형식

:namespace-part/name-part

:table-name/field-name

6.2. keyword examples

:employee/address

:address

:user.type/superuser

:user.type.v2/superuser

:user.type/superuser.boss
:user.type/superuser.manager

6.3. keyword data type의 평가(evaluation)

  • Keyowrd data type is a self-evaluated value.

:employee/address   ;=> :employee/address

7. Datom

  • datom은 Datomic이 데이터를 처리할 때의 기본 단위이다.

7.1. datom의 구성요소

  • 하나의 datom은 1 개의 field를 표현한다.

  • 하나의 datom은 5 개의 구성요소로 이루어져 있다.

    [entity-id attribute value transaction-id added?]

7.2. datom의 구성요소 설명

entity-id

  • SQL DB의 record id (primary key 역할)에 해당.

  • 개발자가 지정해 줄 수 없으며, Datomic이 자동으로 생성. 동일 db 내에서 유일한 값.

  • Java의 long 타입 (64 비트 정수형)

attribute

SQL DB의 field name

value

SQL DB의 field value

transaction-id

동일한 transaction(쓰기)일 때, 동일한 transaction-id 값을 부여 받는다.

added?

  • true: assert or accumulate

  • false: retract

datom들의 실례
[<e-id>   <attribute>         <value>     <tx-id>  <added?>]
...
[   42    :person/firstName   "James"      102     true    ]
[   42    :person/lastName    "Clark"      102     true    ]
[   42    :person/address     43           102     true    ]

[   43    :address/state      "Oregon"     102     true    ]
[   43    :address/city       "Portland"   102     true    ]

[ 1077    :person/fitstName   "Viola"      103     true    ]
[ 1077    :person/lastName    "Davis"      103     true    ]
[ 1077    :person/friends     #{42 89}     103     true    ]
...

7.2.1. SQL table의 record: 2차원 배열

[   42    :person/firstName   "James"      102     true    ]
[   42    :person/lastName    "Clark"      102     true    ]
[   42    :person/gender      "male"       102     true    ]

7.2.2. NoSQL의 document: tree 구조

...
[   42    :person/firstName   "James"      102     true    ]
[   42    :person/lastName    "Clark"      102     true    ]

[   52    :person/firstName   "Alice"      102     true    ]
[   52    :person/lastName    "Parker"     102     true    ]

[ 1077    :person/fitstName   "Viola"      103     true    ]
[ 1077    :person/lastName    "Davis"      103     true    ]
[ 1077    :person/friends     #{42 52}     103     true    ]
...

7.2.3. Graph DB도 구현 가능

7.3. SQL CRUD와의 용어 비교

  • CRUD vs. ARAR

    SQL Datomic Datomic 담당 함수

    Create

    Assert

    transact

    Read

    Read

    q

    Update

    Accumulate

    transact

    Delete

    Retract

    transact

8. Schema

8.1. DB별 비교

  • SQL DB

    • schema를 table 단위로 작성한다.

    • record 구조가 고정되어 있다.

  • Datomic DB

    • schema를 field 단위로 작성한다.

    • record 구조가 고정되어 있지 않다.

      따라서 동적으로 field를 조합하여 record를 만들 수 있다.

    • schema 자체도 datom으로 이루어져 있다.

      따라서 프로그램 실행 중에 동적으로 schema 생성 및 변경이 가능하다.

  • NoSQL DB

    • schema가 아예 없다.

8.2. Datomic Schema의 구성

Datomic Schema의 예
(def schema
  [{:db/ident :user/id
    :db/valueType :db.type/string
    :db/unique :db.unique/value
    :db/cardinality :db.cardinality/one}

   {:db/ident :user/name
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one}

   {:db/ident :user/e-mail
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one}])
Schema Attributes Values

:db/ident

field name을 지정한다. 반드시 keyword 자료형이어야 한다.

:db/valueType

field value의 자료형을 지정힌다.

  • :db.type/string

  • :db.type/long :db.type/bigint

  • :db.type/float :db.type/double :db.type/bigdec

  • :db.type/boolean

  • :db.type/keyword

  • :db.type/ref

  • :db.type/instant

  • :db.type/uuid

  • :db.type/url

  • :db.type/bytes

:db/cardinality

  • :db.cardinality/one - 일대일 대응

  • :db.cardinality/many - 일대다 대응

:db/noHistory

  • true - field value를 누적(accumulate)시키지 않고, 기존의 값을 덮어 쓴다(overwrite).

  • false - default

......

......

8.3. Schema 예제

datomic-guide/schema-example.clj
(ns datomic-guide.schema-example
  (:require [datomic.api :as d]))

;;; database 생성
(def db-uri "datomic:mem://schema-example")

(d/create-database db-uri)

;;; conn 얻기
(def conn (d/connect db-uri))

;;;
;;; schema definitions
;;;
(def schema-1
  [{:db/ident :user/id
    :db/valueType :db.type/string
    :db/unique :db.unique/value
    :db/cardinality :db.cardinality/one}

   {:db/ident :user/name
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one}

   {:db/ident :user/e-mail
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one}])

;;; schema-1 install
@(d/transact conn schema-1)


;;;
;;; data definitions
;;;
(def user-data
  [{:db/id #db/id[:db.part/user]
    :user/id "alice"
    :user/name "Alice Parker"
    :user/e-mail "alice@gmail.com"}

   {:db/id #db/id[:db.part/user]
    :user/id "jack"
    :user/name "Jack Hinton"
    :user/e-mail "jack@gmail.com"}])

;;; data install
@(d/transact conn user-data)


;;;
;;; "alice" 관련 정보 확인
;;;
(defn find-user [id]
  (d/q '[:find ?e .
         :in $ ?id
         :where [?e :user/id ?id]]
        (d/db conn) id))

(def alice-ent-id (find-user "alice"))

alice-ent-id ; => 17592186045418

(d/pull (d/db conn) '[*] alice-ent-id)
; => {:db/id 17592186045418,
;     :user/id "alice",
;     :user/name "Alice Parker",
;     :user/e-mail "alice@gmail.com"}


;;;
;;; field name :user/alias 추가
;;;
(def schema-2
  [{:db/ident :user/alias
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one}])

@(d/transact conn schema-2)


;; "alice"의 :user/alias 필드값 추가
@(d/transact conn [[:db/add alice-ent-id :user/alias "wonderland"]])

;; 추가된 :user/alias 필드값 확인
(d/pull (d/db conn) '[*] alice-ent-id)
; => {:db/id 17592186045418,
;     :user/id "alice",
;     :user/name "Alice Parker",
;     :user/e-mail "alice@gmail.com",
;     :user/alias "wonderland"}


;;;
;;; field name :user/alias --> :user/nickname 으로 변경
;;;
(def alias-ent-id (d/entid (d/db conn) :user/alias))

@(d/transact conn [[:db/add alias-ent-id :db/ident :user/nickname]])

(d/pull (d/db conn) '[*] alice-end-id)
; => {:db/id 17592186045418,
;     :user/id "alice",
;     :user/name "Alice Parker",
;     :user/e-mail "alice@gmail.com",
;     :user/nickname "wonderland"}

8.4. schema도 datom이다

[<e-id>   <attribute>       <value>              <tx-id>   <added?>]
...
[   33    :db/ident         :user/alias          102       true    ]
[   33    :db/type          :db.type/string      102       true    ]
[   33    :db/cardinality   :db.cardinality/one  102       true    ]

[   42    :user/id          "alice"              95        true    ]
[   42    :user/name        "Alice Parker"       95        true    ]
[   42    :user/e-mail      "alice@gmail.com"    95        true    ]

[   42    :user/alias       "wonderland"         205       true    ]
          (실제값은 33)
...

9. Query

9.1. Query language

  • Datomic은 datalog라는 query language를 사용한다. 즉, SQL query 문과 다르다.

  • Datalog = Data + Prolog(Logic programming)

SQL Datomc

query

SQL query (문자열 --> 조작이 쉽지 않다)

Datalog query (Clojure 자료형 --> 조작이 쉽다)

join 방식

명시적

묵시적

9.2. Query examples

schema 요약
:movie/title       :db.type/string   :db.cardinality/one
:movie/year        :db.type/long     :db.cardinality/one
:movie/cast        :db.type/ref      :db.cardinality/many

:person/name       :db.type/string   :db.cardinality/one
:person/born       :db.type/instan   :db.cardinality/one
query 예제
;; SQL query
;;
;; SELECT m.title
;; FROM movies m
;; WHERE m.year = 1987;

(d/q '[:find ?title
       :where [?e :movie/year 1987]
              [?e :movie/title ?title]]
     (d/db conn))
; => #{["RoboCop"] ["Lethal Weapon"] ["Predator"]}


;; query문에서 parameter의 사용
(d/q '[:find ?title
       :in $ ?year
       :where [?e :movie/year ?year]
              [?e :movie/title ?title]]
     (d/db conn) 1987)
; => #{["RoboCop"] ["Lethal Weapon"] ["Predator"]}


;; query문에서 함수의 사용
(d/q '[:find ?title ?year
       :where [?m :movie/title ?title]
              [?m :movie/year ?year]
              [(< ?year 1984)]]
     (d/db conn))
; => #{["Alien" 1979] ["Mad Max" 1979] ["First Blood" 1982] ["Mad Max 2" 1981]}

9.3. Join example

9.3.1. SQL explicit join

SELECT a.account_id, c.gender, e.fname, e.lname
FROM account a INNER JOIN customer c
       ON a.cust_id = c.cust_id
     INNER JOIN employee e
       ON a.emp_id = e.emp_id
WHERE c.cust_type = 'B';
c (customer)
|-- cust_id     (PK)  <--
|-- cust_type           |
`-- gender              |
                        |
a (account)             |
|-- account_id  (PK)    |
|-- cust_id     (FK)  <--
`-- emp_id      (FK)      <--
                            |
e (employee)                |
|-- emp_id      (PK)      <--
|-- fname
`-- lname

9.3.2. Datomic implicit join

(d/q '[:find ?account-id ?gender ?fname ?lname
       :where [?cust-id     :cust/type        "B"]
              [?cust-id     :cust/gender      ?gender]
              [?account-id  :account/cust-id  ?cust-id]
              [?account-id  :account/emp-id   ?emp-id]
              [?emp-id      :emp/fname        ?fname]
              [?emp-id      :emp/lname        ?lname]])

9.3.3. query시 주의할 점

  • query문의 :where절 순서가 중요하다.

다음 두 개의 쿼리 중 어느 쪽이 더 효율적일까?
(d/q '[:find ?cust-id
       :where [?cust-id :cust/gender "female"]
              [?cust-id :cust/type   "B"]])

(d/q '[:find ?cust-id
       :where [?cust-id :cust/type   "B"]
              [?cust-id :cust/gender "female"]])
처리량 메모리 사용량

100만명의 고객 * 1/2 * 1/10 = 5만명의 고객

50만명 + 5만명 = 55만명

100만명의 고객 * 1/10 * 1/2 = 5만명의 고객

10만명 + 5만명 = 15만명

10. DB as a Value & History

10.1. Atom data type in Clojure

Code Diagram
(def a (atom 10))

a   ;=> #atom[10 0x274b9960]
@a  ; => 10

(def b @a)

b   ; => 10
 var          atom            value
=============================================
a -->  #atom[10 0x274b9960] --> 10 (100 번지)
                                 ^
                                 |
b --------------------------------
(reset! a 20)

@a   ; => 20
b    ; => 10
 var          atom            value
============================================
a -->  #atom[20 0x274b9960] --> 20 (200 번지)

b ----------------------------> 10 (100 번지)

10.2. DB as A Value

(ns datomic-guide.time-travel
  (:require [datomic.api :as d]))

;;; db connection
(def db-uri "datomic:mem://time-travel")
(d/create-database db-uri)
(def conn (d/connect db-uri))


(def db-1(d/db conn))

db-1   ; => datomic.db.Db@29475cf8


;; schema install
(def schema
  [{:db/ident :user/id
    :db/valueType :db.type/string
    :db/unique :db.unique/value
    :db/cardinality :db.cardinality/one}

   {:db/ident :user/name
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one}

   {:db/ident :user/e-mail
    :db/valueType :db.type/string
    :db/unique :db.unique/identity
    :db/cardinality :db.cardinality/one}])

@(d/transact conn schema)
; => {:db-before datomic.db.Db@29475cf8,
;     :db-after datomic.db.Db@5ba75b5c,
;     :tx-data [#datom[13194139534312 50 #inst "2017-11-09T06:03:29.648-00:00" 13194139534312 true]
;               #datom[63 10 :user/id 13194139534312 true]
;               #datom[63 40 23 13194139534312 true]
;               #datom[63 42 37 13194139534312 true]
;               #datom[63 41 35 13194139534312 true]
;               #datom[64 10 :user/name 13194139534312 true]
;               #datom[64 40 23 13194139534312 true]
;               #datom[64 41 35 13194139534312 true]
;               #datom[65 10 :user/e-mail 13194139534312 true]
;               #datom[65 40 23 13194139534312 true]
;               #datom[65 42 38 13194139534312 true]
;               #datom[65 41 35 13194139534312 true]
;               #datom[0 13 65 13194139534312 true]
;               #datom[0 13 64 13194139534312 true]
;               #datom[0 13 63 13194139534312 true]],
;     :tempids {-9223301668109598143 63, -9223301668109598142 64, -9223301668109598141 65}}

(def db-2 (d/db conn))

db-2   ; => datomic.db.Db@5ba75b5c


;; data install
(def user-data
  [{:db/id #db/id[:db.part/user]
    :user/id "alice"
    :user/name "Alice Parker"
    :user/e-mail "alice@gmail.com"}

   {:db/id #db/id[:db.part/user]
    :user/id "jack"
    :user/name "Jack Hinton"
    :user/e-mail "jack@gmail.com"}])

@(d/transact conn user-data)

(def db-3 (d/db conn))

db-3   ; => datomic.db.Db@d34f836c


(def alice-ent-id
  (d/q '[:find ?e .
         :where [?e :user/id "alice"]]
       db-3))

(d/pull db-3 '[*] alice-ent-id)
; => {:db/id 17592186045418,
;     :user/id "alice",
;     :user/name "Alice Parker",
;     :user/e-mail "alice@gmail.com"}



@(d/transact conn [[:db/add alice-ent-id :user/e-mail "alice@facebook.com"]])

(def db-4 (d/db conn))

(d/pull db-4 '[*] alice-ent-id)
; => {:db/id 17592186045418,
;     :user/id "alice",
;     :user/name "Alice Parker",
;     :user/e-mail "alice@facebook.com"}

10.3. History DB

(def hist-db (d/history db-4))

(d/q '[:find ?e ?e-mail ?tx
       :in $hist
       :where [$hist ?e :user/id "alice"]
              [$hist ?e :user/e-mail ?e-mail]]
     hist-db)
; => #{[17592186045418 "alice@gmail.com"]
;      [17592186045418 "alice@facebook.com"]}

11. 한계

  • not open-source, free-as-free-beer.

  • memory 사용량이 많다.

12. 참고 자료

  1. http://www.learndatalogtoday.org

    • Datomic에서 사용하고 있는 Datalog 방식의 query를 간단히 소개

  2. Professional Clojure, Chapter 6

    • 현재까지 나온 Clojure 관련 책들 중에서 Datomic에 대해 상대적으로 가장 자세하게 소개

  3. http://docs.datomic.com/index.html

    • Datomic 공식 문서 site

  4. https://github.com/Datomic/day-of-datomic

    • 다양하고 풍부한 예제 제공

    • 특히 tutorial 폴더에 Datomic이 제공하는 거의 모든 API에 대한 예제가 담겨 있다.

About

Datomic introduction (in Korean)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published