https://github.com/philoskim/datomic-intro
Datomic introduction (in Korean)
https://github.com/philoskim/datomic-intro
Last synced: 15 days ago
JSON representation
Datomic introduction (in Korean)
- Host: GitHub
- URL: https://github.com/philoskim/datomic-intro
- Owner: philoskim
- Created: 2017-11-13T00:39:28.000Z (about 8 years ago)
- Default Branch: master
- Last Pushed: 2017-11-13T00:59:11.000Z (about 8 years ago)
- Last Synced: 2024-08-04T01:17:12.247Z (over 1 year ago)
- Language: Clojure
- Size: 210 KB
- Stars: 12
- Watchers: 3
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.adoc
Awesome Lists containing this project
- awesome-list - datomic-intro
README
= Datomic: 함수형 데이터베이스
:sectnums:
:source-language: clojure
:source-highlighter: coderay
:coderay-css: class
:imagesdir: img/
:latexmath:
== 강사 소개
* 이름: 김영태
* e-mail: philos99@gmail.com
* link:http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9788966261802&orderClick=LAG&Kc=[클로저 시작하기] 공역, 인사이트 (원서: link:https://www.amazon.com/Living-Clojure-Introduction-Training-Developers/dp/1491909048/ref=sr_1_1?ie=UTF8&qid=1509447512&sr=8-1&keywords=living+clojure[Living Clojure], Carin Meier, O'Reilly)
* link:https://github.com/philoskim/debux[debux] (Clojure/ClojureScript 디버깅 라이브러리) 제작
** 예제: link:https://github.com/philoskim/debux#simple-example[]
== Datomic 소개
* Clojure 언어의 창시자 Rich Hickey가 만든 함수형 데이터베이스
* Clojure 언어로 쓰여져 있다.
* "Immutable" Database: 개념상 git과 유사
* 2012년에 최초 버전(0.8.3335) 발표
* 현재 최신 버전: 0.9.5561.62
=== 참고: link:https://github.com/tonsky/datascript[DataScript]
* Immutable memory database and Datalog query engine for Clojure, ClojureScript and JavaScript
* 서버에서 사용되는 Datomic을 모방해 만들었다.
* 주로 Web Browser Client용으로 사용된다.
* Datomic과 거의 동일한 API를 제공한다.
== Datomic 도입 사례
* link:http://blog.cognitect.com/blog/2015/6/30/walmart-runs-clojure-at-scale[Walmart runs Clojure and Datomic at scale]
* link:http://blog.datomic.com/2015/09/nubank-chooses-datomic.html[Nubank chooses Datomic]
* link:http://www.datomic.com/customers.html[기타 Customers Story]
== 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등
== 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.
image::datomic-architecture.png[]
[listing]
.Datomic DB vs. SQL DB
----
Datomic DB SQL DB
=============================================
|-- Storage
|
|-- Transactor
| |-- Writing
| `-- Indexing SQL Server
|
`-- Peer Library
|-- Live index
|-- Cache
====|========================================
`-- Query SQL Client Library
=============================================
----
== Keyword data type in Clojure
=== keyword data type 형식
[listing]
----
:namespace-part/name-part
:table-name/field-name
----
=== keyword examples
[source]
....
:employee/address
:address
:user.type/superuser
:user.type.v2/superuser
:user.type/superuser.boss
:user.type/superuser.manager
....
=== keyword data type의 평가(evaluation)
* Keyowrd data type is a self-evaluated value.
[listing]
----
:employee/address ;=> :employee/address
----
== Datom
* datom은 Datomic이 데이터를 처리할 때의 기본 단위이다.
=== datom의 구성요소
* 하나의 datom은 1 개의 field를 표현한다.
* 하나의 datom은 5 개의 구성요소로 이루어져 있다.
+
[listing]
----
[entity-id attribute value transaction-id added?]
----
=== datom의 구성요소 설명
[width="75%",cols="1,3"]
|===
| `entity-id`
a|
* 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?`
a|
* `true`: assert or accumulate
* `false`: retract
|===
[listing,subs="macros,quotes"]
.datom들의 실례
----
+[ ]+
...
[ 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` <>
==== SQL table의 record: 2차원 배열
[listing]
----
[ 42 :person/firstName "James" 102 true ]
[ 42 :person/lastName "Clark" 102 true ]
[ 42 :person/gender "male" 102 true ]
----
==== NoSQL의 document: tree 구조
[listing]
----
...
[ 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 ]
...
----
==== Graph DB도 구현 가능
=== SQL CRUD와의 용어 비교
* CRUD vs. ARAR
+
[%header,width="75%",cols="3*^"]
|===
| SQL | Datomic | Datomic 담당 함수
| Create | Assert | `transact`
| Read | Read | `q`
| Update | Accumulate | `transact`
| Delete | Retract | `transact`
|===
== Schema
=== DB별 비교
* SQL DB
** schema를 table 단위로 작성한다.
** record 구조가 고정되어 있다.
* Datomic DB
** schema를 field 단위로 작성한다.
** record 구조가 고정되어 있지 않다.
+
따라서 동적으로 field를 조합하여 record를 만들 수 있다.
** schema 자체도 datom으로 이루어져 있다.
+
따라서 프로그램 실행 중에 동적으로 schema 생성 및 변경이 가능하다.
* NoSQL DB
** schema가 아예 없다.
=== Datomic Schema의 구성
[source]
.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}])
....
[%header,cols="1,3a"]
|===
| Schema Attributes | Values
|`:db/ident` | field name을 지정한다. 반드시 keyword 자료형이어야 한다.
|`:db/valueType` [[db-type-ref]]
| 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
| pass:q[......]
| pass:q[......]
|===
=== Schema 예제
[source]
.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"}
....
=== schema도 datom이다
[listing,subs="macros,quotes"]
----
+[ ]+
...
[ #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#)
...
----
== Query
=== Query language
* Datomic은 datalog라는 query language를 사용한다. 즉, SQL query 문과 다르다.
* Datalog = Data + Prolog(Logic programming)
[%header,cols="1,2,2"]
|===
| ^| SQL ^| Datomc
| query | SQL query (문자열 pass:q[-->] 조작이 쉽지 않다) | Datalog query (Clojure 자료형 pass:q[-->] 조작이 쉽다)
| join 방식 | 명시적 | 묵시적
|===
=== Query examples
[source]
.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
....
[source,subs="macros,quotes"]
.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]}
....
=== Join example
==== SQL explicit join
[listing]
----
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';
----
[listing,subs="macros,quotes"]
----
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*
----
==== Datomic implicit join
[source,subs="macros,quotes"]
....
(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시 주의할 점
* query문의 ``:where``절 순서가 중요하다.
[source]
.다음 두 개의 쿼리 중 어느 쪽이 더 효율적일까?
....
(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"]])
....
[%header,width="75%",cols="2,^.^1"]
|===
^| 처리량 ^| 메모리 사용량
| 100만명의 고객 * 1/2 * 1/10 = 5만명의 고객
| 50만명 + 5만명 = 55만명
| 100만명의 고객 * 1/10 * 1/2 = 5만명의 고객
| 10만명 + 5만명 = 15만명
|===
== DB as a Value & History
=== Atom data type in Clojure
[%header,cols="1a,1a"]
|===
^| Code ^| Diagram
| [source]
....
(def a (atom 10))
a ;=> #atom[10 0x274b9960]
@a ; => 10
(def b @a)
b ; => 10
....
| [listing]
----
var atom value
=============================================
a --> #atom[10 0x274b9960] --> 10 (100 번지)
^
\|
b --------------------------------
----
|
[source]
....
(reset! a 20)
@a ; => 20
b ; => 10
....
| [listing]
----
var atom value
============================================
a --> #atom[20 0x274b9960] --> 20 (200 번지)
b ----------------------------> 10 (100 번지)
----
|===
=== DB as A Value
[source]
....
(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"}
....
=== History DB
[source]
....
(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"]}
....
== 한계
* not open-source, free-as-free-beer.
* memory 사용량이 많다.
== 참고 자료
. link:http://www.learndatalogtoday.org[]
** Datomic에서 사용하고 있는 Datalog 방식의 query를 간단히 소개
. link:https://www.amazon.com/Professional-Clojure-Jeremy-Anderson/dp/1119267277/ref=sr_1_1?ie=UTF8&qid=1510200830&sr=8-1&keywords=professional+clojure[Professional Clojure], Chapter 6
** 현재까지 나온 Clojure 관련 책들 중에서 Datomic에 대해 상대적으로 가장 자세하게 소개
. link:http://docs.datomic.com/index.html[]
** Datomic 공식 문서 site
+
. link:https://github.com/Datomic/day-of-datomic[]
** 다양하고 풍부한 예제 제공
** 특히 link:https://github.com/Datomic/day-of-datomic/tree/master/tutorial[tutorial] 폴더에 Datomic이 제공하는 거의 모든 API에 대한 예제가 담겨 있다.