Quora에서 받은 이메일 중에 Microsoft의 새로운 인터뷰 프로세스에 대한 이야기가 있었다. 뭔가 싶어 블로그를 찾아 읽어보니 꽤 흥미로워서 링크를 남깁니다. 자세한 내용은 아래 링크에서 확인해보세요.


Microsoft's New Approach to Job Interviews Makes People Really Want to Work There

https://www.inc.com/minda-zetlin/microsoft-changes-job-interview-process-no-more-brain-teasers.html


그 동안 꽤 많은 인터뷰를 해봤다. 캐나다 영국 미국에서 모두 오퍼를 받아봤다. 보통 인터뷰 프로세스는 즐겁지 않다. 그래서 목표한 회사가 있어도 다른 곳에서 먼저 오퍼를 받으면 수락해버렸다. 어쩌면 Microsoft의 이런 방식은 인터뷰이로서 인터뷰를 즐길 수도 있는 기회가 될 것 같기도 하다.



Posted by 코딩새싹
,

Clojure에서는 병렬성(Parallelism)을 지원하는 라이브러리인 Reducers가 있다. Reducers를 이용하면 쓰레드를 이용해 동시에 데이터에 작업을 수행하기 때문에 속도 면에서 이득을 볼 수 있다. Reducers를 이용한 장점은 쓰레드 사용하는 것 외에도 중간 데이터를 만들지 않는다는 또 다른 장점도 있다. 이것은 나중에.


아래 예제는 reduce 함수를 써서 구할 수 있는 결과를 fold를 이용해 속도를 올린 것이다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(require '[clojure.core.reducers :as r])
 
(def nums (vec (doall (range 1000000))))
 
(defn mean-combiner
  ([] {:count 0 :sum 0})
  ([acc cur] (merge-with + acc cur)))
 
(defn mean-reducer
  [acc cur]
  (-> acc
      (update-in [:count] inc)
      (update-in [:sum] + cur)))
 
(defn get-mean
  [nums]
  (r/fold mean-combiner mean-reducer nums))
 
(defn get-mean-r
  [nums]
  (reduce mean-reducer {:count 0 :sum 0} nums))
cs


fold 함수를 제대로 사용하기 위해서는 foldable collection인 vector나 map을 입력해야 한다. 그렇지 않을 경우 non-parellel reduce로 폴백해 실행한다. 결과는 reducible이라는 형태로 리턴하기 때문에 값을 이용하기 위해서는 into 함수나 r/foldcat 함수를 써야 한다. 


위 코드를 실행하면 아래와 같은 결과를 볼 수 있다. 

user=> (time (get-mean nums))

"Elapsed time: 143.152352 msecs"

{:count 1000000, :sum 499999500000}


user=> (time (get-mean-r nums))

"Elapsed time: 390.559946 msecs"

{:count 1000000, :sum 499999500000}


첫번째 실행은 r/fold를 사용한 예이며 아래는 reduce를 사용한 예이다. 응답시간을 보면 fold의 장점을 확실히 알 수 있다. 단순한 경우이지만 데이터가 크고 복잡한 연산이 많을 경우 큰 성능 상의 이점은 더욱 분명해질 것이라고 본다.


fold는 combiner 함수와 reducer 함수를 컬렉션과 함께 인자로 받는데 이 때 reducer 함수는 보통(default) 512개의 단위로 나뉜 컬랙션 조각에 호출되는 함수이고 combiner 함수는 reducer 함수의 결과에 호출돼 결과를 하나로 합치는 함수이다. 예를 들어 입력 데이터가 10개, 데이터 2개마다 쓰래드를 호출한다고 가정하면 reducer함수는 5개 조각에서 각각 실행돼 5개의 결과를 만들어 낸다. 이 5개의 결과가 combiner 함수에 의해 하나의 결과로 합쳐진다. 


위 예제에서는 mean-reducer라는 함수가 각 조각을 이용해  {:count 512 :sum: SOME_VALUE} 라는 결과를 만들어낸다. 조각의 개수 만큼 만들어진 결과에는 mean-combiner 함수가 호출되는데 이 때 merge-with 함수를 써서 맵 프로퍼티들에 + 함수를 호출해 값을 합치게 된다.


좀 더 자세한 내용은 아래의 링크를 참고하세요.


https://clojuredocs.org/clojure.core.reducers/fold

http://clojuredatascience.com/posts/2015-09-12-parallel-folds-reducers-tesser-linear-regression.html



Posted by 코딩새싹
,

Clojure 앱에서 데이터베이스에 접속해 쿼리를 실행하는 방법은 여러 가지가 있다. Korma를 이용해도 되고 jdbc를 이용해도 된다. 이번에는 clojure.jdbc를 이용한 데이터베이스 쿼리하기를 정리해보기로 한다. 


korma

http://sqlkorma.com


clojure.jdbc

http://funcool.github.io/clojure.jdbc/latest


프로젝트 생성

clojure.jdbc는 jdbc 기반의 로우레벨 라이브러리다. 이 라이브러리를 사용하기 위해 lein으로 생성한 프로젝트의 project.clj 파일에 아래와 같이 의존성을 추가한다.

[funcool/clojure.jdbc "0.9.0"]

[mysql/mysql-connector-java "5.1.6"]


그리고 c3p0의 Connection pool 기능을 사용하기 위해 아래와 같은 의존성을 추가한다.

[com.mchange/c3p0 "0.9.5"]


프로젝트 폴더에 다음과 같은 파일들을 추가한다. 

config

  - dev

    - config.edn

src

  - model

    - users.clj

  - app.clj

  - db.clj

  - settings.clj


config/dev/config.edn

데이터베이스 접속과 관련된 credentials을 코드로부터 분리하기 위해 config.edn 파일을 추가했다. 이와 관련된 내용은 아래 링크의 내용을 참고하세요.


Clojure 앱에서 환경변수 사용 방법

https://iamcool.tistory.com/11


settings.clj 

settings.clj 파일은 config/dev/config.edn 파일을 읽어와 db-spec, redis-spec을 만든다. db-spec 파일은 url, user, password로 구성되어 있으며 db.clj 파일에서 사용된다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(ns settings
  (:require [config.core :refer [env]]))
 
(defn create-db-spec [runtime-env-db]
  (let [{subprotocol :dbtype
         host :host
         port :port
         user :user
         password :password
         dbname :dbname} runtime-env-db
         subname (format "//%s:%s/%s" host port dbname)]
    {:url (str "jdbc:" subprotocol ":" subname)
     :user user
     :password password}))
 
(def db-spec (create-db-spec (get-in env [:runtime :db])))
(def redis-spec (get-in env [:runtime :redis]))
cs


db.clj 

db.clj 파일은 settings.clj 파일로부터 db-spec을 가져와 c3p0 pooling DataSource를 만든다. 우리는 이 DataSource로부터 jdbc connection을 만들어 사용한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
(ns db
  (:require [settings :refer [db-spec]]
            [jdbc.core :as jdbc])
  (:import [java.sql Connection DriverManager]
           (com.mchange.v2.c3p0 ComboPooledDataSource)))
 
(defn get-datasource [db-spec]
  (let [{url :url
         user :user
         password :password} db-spec]
    (doto (ComboPooledDataSource.)
              (.setDriverClass "com.mysql.jdbc.Driver")
              (.setJdbcUrl url)
              (.setUser user)
              (.setPassword password)
              ;; Pool Size Management
              (.setMinPoolSize 2)
              (.setMaxPoolSize 20)
              (.setAcquireIncrement 5)
              (.setMaxStatements 180)
              ;; Connection eviction
              (.setMaxConnectionAge 3600)
              (.setMaxIdleTime 1800)
              (.setMaxIdleTimeExcessConnections 120)
              ;; Connection testing
              (.setTestConnectionOnCheckin false)
              (.setTestConnectionOnCheckout false)
              (.setIdleConnectionTestPeriod 600))))
 
(def ds (get-datasource db-spec))
(defn db-conn [] (jdbc/connection ds))
cs


이 때 사용되는 db-spec은 config/dev/config.edn으로부터 만들어졌으며 다음과 같은 값을 가진다.

{:url "jdbc:mysql://localhost:3306/dbname"

 :user"user"

 :password "password"}


커넥션풀과 관련된 설정은 c3p0 사이트를 참고하세요.


c3p0

https://www.mchange.com/projects/c3p0


models/users.clj

이 파일은 db.clj 파일로부터 jdbc connection을 가져와 test_user 데이터베이스에 생성한 users 테이블에 CRUD 작업을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(ns models.users
  (:use [jdbc.core :as jdbc]
        [db :refer [db-conn]]))
 
(defn read-user [data]
  (with-open [conn (db-conn)]
    (let [{username :username} data
          query "SELECT id, username, email, title, first_name, surname, created_at, updated_at
                 FROM users
                 WHERE username = ?"]
     (jdbc/fetch conn [query username]))))
 
(defn create-user [row]
  (with-open [conn (db-conn)]
    (let [{username :username
           email :email
           title :title
           first_name :first_name
           surname :surname} row
          query "INSERT INTO users (username, email, title, first_name, surname)
                 VALUES (?, ?, ?, ?, ?)"]
     (jdbc/execute conn [query username, email, title, first_name, surname]))))
 
(defn delete-user [data]
  (with-open [conn (db-conn)]
    (let [{username :username} data
          query "DELETE FROM users WHERE username = ?"]
     (jdbc/execute conn [query username]))))
cs


테스트

테스트를 위해 아래 명령을 실행한다.

lein deps

lein run


명령이 실행되면 앱은 test_user 데이터베이스의 users 테이블에 접속하여 새로운 유저를 생성, 조회한 뒤 삭제한다. 아래 로그를 참고하세요.

Create users!

// JDBC CONNECTION LOG

Read users!

[{:id 17, :username user1, :email user1@example.com, :title Mr, :first_name James, :surname Kook, :created_at #inst "2019-01-15T22:47:02.000000914-00:00", :updated_at #inst "2019-01-15T22:47:02.000000914-00:00"}]

Delete users!


소스코드

간단한 예제이지만 여기에 전체 소스코드를 올려놓았다.


https://github.com/ksleeq21/clojure-db-example


처음에는 korma를 이용해 개발을 했었는데 조금 복잡한 쿼리를 만들기가 어려웠다. 예전에 후배가 Clojure를 쓰다보면 Java를 찾게 된다더니 그 기분을 조금 이해하게 됐다.



Posted by 코딩새싹
,