-
이름: 김영태
-
e-mail: [email protected]
-
클로저 시작하기 공역, 인사이트 (원서: Living Clojure, Carin Meier, O’Reilly)
-
debux (Clojure/ClojureScript 디버깅 라이브러리) 제작
-
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를 제공한다.
-
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등
-
-
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 DB SQL DB ============================================= |-- Storage | |-- Transactor | |-- Writing | `-- Indexing SQL Server | `-- Peer Library |-- Live index |-- Cache ====|======================================== `-- Query SQL Client Library =============================================
:employee/address
:address
:user.type/superuser
:user.type.v2/superuser
:user.type/superuser.boss
:user.type/superuser.manager
-
datom은 Datomic이 데이터를 처리할 때의 기본 단위이다.
-
하나의 datom은 1 개의 field를 표현한다.
-
하나의 datom은 5 개의 구성요소로 이루어져 있다.
[entity-id attribute value transaction-id added?]
|
|
|
SQL DB의 field name |
|
SQL DB의 field value |
|
동일한 transaction(쓰기)일 때, 동일한 |
|
|
[<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 ] ...
-
(참고)
:db.type/ref
[db-type-ref]
[ 42 :person/firstName "James" 102 true ] [ 42 :person/lastName "Clark" 102 true ] [ 42 :person/gender "male" 102 true ]
... [ 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 ] ...
-
SQL DB
-
schema를 table 단위로 작성한다.
-
record 구조가 고정되어 있다.
-
-
Datomic DB
-
schema를 field 단위로 작성한다.
-
record 구조가 고정되어 있지 않다.
따라서 동적으로 field를 조합하여 record를 만들 수 있다.
-
schema 자체도 datom으로 이루어져 있다.
따라서 프로그램 실행 중에 동적으로 schema 생성 및 변경이 가능하다.
-
-
NoSQL DB
-
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 |
---|---|
|
field name을 지정한다. 반드시 keyword 자료형이어야 한다. |
field value의 자료형을 지정힌다.
|
|
|
|
|
|
...... |
...... |
(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 "[email protected]"}
{:db/id #db/id[:db.part/user]
:user/id "jack"
:user/name "Jack Hinton"
:user/e-mail "[email protected]"}])
;;; 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 "[email protected]"}
;;;
;;; 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 "[email protected]",
; :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 "[email protected]",
; :user/nickname "wonderland"}
[<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 "[email protected]" 95 true ] [ 42 :user/alias "wonderland" 205 true ] (실제값은 33) ...
-
Datomic은 datalog라는 query language를 사용한다. 즉, SQL query 문과 다르다.
-
Datalog = Data + Prolog(Logic programming)
SQL | Datomc | |
---|---|---|
query |
SQL query (문자열 --> 조작이 쉽지 않다) |
Datalog query (Clojure 자료형 --> 조작이 쉽다) |
join 방식 |
명시적 |
묵시적 |
: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
;; 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]}
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
(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]])
-
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만명 |
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 번지) |
(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 "[email protected]"}
{:db/id #db/id[:db.part/user]
:user/id "jack"
:user/name "Jack Hinton"
:user/e-mail "[email protected]"}])
@(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 "[email protected]"}
@(d/transact conn [[:db/add alice-ent-id :user/e-mail "[email protected]"]])
(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 "[email protected]"}
(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 "[email protected]"]
; [17592186045418 "[email protected]"]}
-
http://www.learndatalogtoday.org
-
Datomic에서 사용하고 있는 Datalog 방식의 query를 간단히 소개
-
-
Professional Clojure, Chapter 6
-
현재까지 나온 Clojure 관련 책들 중에서 Datomic에 대해 상대적으로 가장 자세하게 소개
-
-
http://docs.datomic.com/index.html
-
Datomic 공식 문서 site
-
-
https://github.com/Datomic/day-of-datomic
-
다양하고 풍부한 예제 제공
-
특히 tutorial 폴더에 Datomic이 제공하는 거의 모든 API에 대한 예제가 담겨 있다.
-