Slick 개발시 아래와 같이 * method의 구현 코드를 작성하게 된다.

Table 생성시 (Int, String)의 Tuple type에 정의된 순서에 따라 (id, firstName) 형태의 구현이 필요하며,

정의된 Type의 범위를 벗어나는 경우 compile error가 발생한다.

class Hell extends Table[(Int, String)]("TB_HELL") {
  def id = column[Int]("HELL_ID")
  def firstName = column[String]("FIRST_NAME")

  def * = (id, firstName)
}

위의 코드에서 method * 는 column의 return type이 Rep class로 구성된 Tuple의 형태로 구현 혹은 할당된 모습으로 보인다.

column[C]의 정의는 아래와 같으며 Rep[C]를 return 하고 있다.

def column[C](n: String, options: ColumnOption[C]*)(implicit tt: TypedType[C]): Rep[C] = {
    /** ... **/
}

그러면 def *의 정의 구조를 살펴보자.

abstract class AbstractTable[T](val tableTag: Tag, val schemaName: Option[String], val tableName: String) extends Rep[T] {
  
  /** ... **/

  /** The * projection of the table used as default for queries and inserts.
    * Should include all columns as a tuple, HList or custom shape and optionally
    * map them to a custom entity type using the <> operator.
    * The `ProvenShape` return type ensures that
    * there is a `Shape` available for translating between the `Column`-based
    * type in * and the client-side type without `Column` in the table's type
    * parameter. */
  def * : ProvenShape[T] 
}

위와 같이 AbstractTable[T]의 내부 method로 정의되며 ProvenShape[T]가 Return Type이다.

 

그리면 이어서 ProvenShape[T]를 살펴보자.

trait ProvenShape[U] {
  def value: Any
  val shape: Shape[_ <: FlatShapeLevel, _, U, _]
  def packedValue[R](implicit ev: Shape[_ <: FlatShapeLevel, _, U, R]): ShapedValue[R, U]
  def toNode = packedValue(shape).toNode
}

위와 같은 형태의 trait으로 추론해보면,

(Rep[Int], Rep[String]) 형태의 원천 Tuple 데이터를 ProvenShape[(Int, String)] 형태로 변환하는 implicit type converstion이 어디인가 정의되어 있음을 추정할 수 있다.

(Rep[Int], Rep[String]) ------> ProvenShape[(Int, String)]

결론부터 말하자면,

적용된 implicit method를 명시적으로 호출되도록 구현을 하면 본질적으로 아래와 같은 호출 & 변환 구조라는 것을 알게 된다.

def * = ProvenShape.proveShapeOf(id, firstName)
		(tuple2Shape(repColumnShape(intColumnType), repColumnShape(stringColumnType)))

전체 implicit의 전체 처리 구조를 보기 좋게 재정리하면 아래와 같다.

눈에 보이는 것을 제외하곤 모든 것이 implicit 이다.

def * = ProvenShape.proveShapeOf(id, firstName)
       (
            tuple2Shape(
                repColumnShape(
                    intColumnType
                ), 
                repColumnShape(
                    stringColumnType
                )
            )
       )

 

우선 최상위 implicit 변환인 provenShapeOf 부터 살펴보면,

해당 변환의 정의는 object ProvenShape에서 찾을 수 있으며, 해당 object는 trait ProvenShape[T]의 동반 객체(Companion Object)로 해당 object내에 정의된 implicit def는 별도의 import 없이 ProvenShape[T]를 사용하는 스코프내에서 처리될 수 있다.

object ProvenShape {
  /** Convert an appropriately shaped value to a ProvenShape */
  implicit def proveShapeOf[T, U](v: T)(implicit sh: Shape[_ <: FlatShapeLevel, T, U, _]): ProvenShape[U] =
    new ProvenShape[U] {
      def value = v
      val shape: Shape[_ <: FlatShapeLevel, _, U, _] = sh
      def packedValue[R](implicit ev: Shape[_ <: FlatShapeLevel, _, U, R]): ShapedValue[R, U] = ShapedValue(sh.pack(value).asInstanceOf[R], sh.packedShape.asInstanceOf[Shape[FlatShapeLevel, R, U, _]])
    }
    
  /** ... **/
}

정의된 Tuple의 값이 아래의 provenShapeOf(A)(B) function을 통해 최종적으로는 ProvencShape[U]로 변환 처리됨을 알 수 있다.

implicit def proveShapeOf[T, U](v: T)
	(implicit sh: Shape[_ <: FlatShapeLevel , T, U, _]): ProvenShape[U]

 

provenShapeOf는 2개의 parameter를 필요로 하는 currying이 적용된 method이다.

 

최초의 샘플코드를 기준으로 하나하나 살펴보면,

위의 Type T는 (Rep[Int], Rep[String])의 Tuple2에 해당되며, Type U는 (Int, String)의 Tuple2에 해당된다.

implicit parameter인 Shape는 Shape[Level, (M1,M2)(U1,U2), (P1,P2)]의 형태로 대응될 수 있다. 

 

하나하나 연관관계를 대응해보면 아래와 같이 표현할 수 있다.

ProvenShape.proveShapeOf(M1, M2)(Shape[Level, (M1,M2), (U1,U2), (P1,P2)])

M1 => Rep[Int]
M2 => Rep[String]
U1 => Int
U2 => String

 

첫번째 parameter는,

(v: T)의 T는 앞서 정의한 입력 parameter 값에 따라 정해지는 Type으로 (Rep[Int], Rep[String])의 형태의 두개의 값을 가지는 Tuple2의 Type으로 정의될 수 있다. (T = Tuple2)

 

두번째 parameter는,

아래와 같이 implicit 생성 규칙에 의해 생성된 Shape instance 이다.

호출시 사용된 parameter의 type인 Tuple2가 Type 인자 T로 정의되어 있음을 알 수 있고 Table 최초 정의시의  Schema인 (Int, String)이 U로 대응되는 Shape instance가 생성되어 있어야 함을 의미한다. 

(implicit sh: Shape[_ <: FlatShapeLevel , T, U, _])

위의 Shape의 구조와 instance을 생성하는 implicit 의 구현을 살펴보자. 

Shape object에 3개의 implicits의 trait을 상속 받고 있으며, Tuple 구조의 데이터를 처리하는 TupleShapeImplicit를 역시 상속받고 있다.

모든 Shape instance에서 해당 traits이 적용 범위가 될 수 있음을 의미한다.

 

앞서 설명하였던 Tuple 이외에 다른 형태의 자료형을 처리하는 Implicit가 다른 trait에 정의 되어있음을 추정할 수 있다. 

(내용이 방대한 관계로 여기서는 Tuple을 처리하는 implicit 만 소개하도록 하겠다.)

abstract class Shape[Level <: ShapeLevel, -Mixed_, Unpacked_, Packed_] {
  /** ... **/
}

object Shape extends ConstColumnShapeImplicits 
	with AbstractTableShapeImplicits with TupleShapeImplicits {
  /** ... **/
}

trait TupleShapeImplicits {
  @inline
  implicit final def tuple2Shape[Level <: ShapeLevel, M1,M2, U1,U2, P1,P2]
  	(implicit u1: Shape[_ <: Level, M1, U1, P1], 
    		u2: Shape[_ <: Level, M2, U2, P2])
    : Shape[Level, (M1,M2), (U1,U2), (P1,P2)] =
    new TupleShape[Level, (M1,M2), (U1,U2), (P1,P2)](u1,u2)
    
  @inline
  implicit final def tuple3Shape[Level <: ShapeLevel, M1,M2,M3, U1,U2,U3, P1,P2,P3]
  	(implicit u1: Shape[_ <: Level, M1, U1, P1], 
    		u2: Shape[_ <: Level, M2, U2, P2], 
        	u3: Shape[_ <: Level, M3, U3, P3])
    : Shape[Level, (M1,M2,M3), (U1,U2,U3), (P1,P2,P3)] =
    new TupleShape[Level, (M1,M2,M3), (U1,U2,U3), (P1,P2,P3)](u1,u2,u3)
    
  /** ... **/
}

final class TupleShape[Level <: ShapeLevel, M <: Product, U <: Product, P <: Product]
	(val shapes: Shape[_ <: ShapeLevel, _, _, _]*) 
    extends ProductNodeShape[Level, Product, M, U, P] {
    
  override def getIterator(value: Product) = value.productIterator
  def getElement(value: Product, idx: Int) = value.productElement(idx)
  def buildValue(elems: IndexedSeq[Any]) = TupleSupport.buildTuple(elems)
  def copy(shapes: Seq[Shape[_ <: ShapeLevel, _, _, _]])  = new TupleShape(shapes: _*)
}

일단은 위의 tuple2Shape implicit 함수가 아래의 sh implicit parameter의 값(instacne)를 생성한다고 볼 수 있다.

implicit def proveShapeOf[T, U](v: T)
	(implicit sh: Shape[_ <: FlatShapeLevel , T, U, _]): ProvenShape[U]

Type이 너무 많은 코드라 복잡하고 뭐가 뭔지 알 수가 없다. 갈 길을 잃을 타이밍이나 아래의 내용만 명심하면 된다.

 

tuple2Shape는 tuple2의 type구조를 반영하는 Shape 객체를 그저 만들어 내는 역할을 한다.

 

TupleShapeImplicits trait 파일을 실제로 살펴보면 Tuple 의 갯수별로 implicit function이 개별적으로 구현되어 있는데 입력되는 Tuple의 갯수가 한정적이고, 데이터 갯수에 따라서 1:1로 implicit conversion이 정의되어 있음을 알 수 있다. 

 

여기서 드는 의문은 동일한 TupeShape Class의 객체라 할지라도 선언된 Type parameter와 문맥이 일치하는 객체를 찾아 implicit converstion에 적용할 수 있느냐 이다. 아래의 예제를 살펴보자.

object TypeImplicits {
  abstract class Shape[A,B,C,D] {
    type RepType = B
//    def print = println("Type" + this.asInstanceOf[B])
    def print
  }

  trait ProvenShape[T] {
    def dsp
  }

  def main(args: Array[String]): Unit = {
    implicit def shapeA[A,B1,B2,C1,C2,D] : Shape[A, (B1,B2), (C1,C2), (D, D)] =
      new Shape[A,(B1,B2),(C1,C2),(D, D)] {
        def print = println("Tuple2 Shape")
      }
    implicit def shapeB[A,B1,B2,B3,C1,C2,C3,D] : Shape[A, (B1,B2,B3), (C1,C2,C3), (D, D)] =
      new Shape[A, (B1,B2,B3), (C1,C2,C3), (D, D)] {
        def print = println("Tuple3 Shape")
      }

    implicit def shapeOf[T, U](t : T)(implicit sh: Shape[_, T, U, _]) : ProvenShape[U] = 
      new ProvenShape[U] {
        def dsp = sh.print
      }

    val aa : ProvenShape[(Int, Int)] = (1, 2)
    aa.dsp
    val aaa : ProvenShape[(String, String, String)] = ("A", "B", "C")
    aaa dsp
  }
}

output:
Tuple2 Shape
Tuple3 Shape

implicit는 생각 이상으로 똑똑하다. 최조 정의된 data의 Type에 따라서 shapeA와 shapeB의 Type에 맞춰 구분하여 implicit conversion이 적용된다.

 

다시 본론으로 돌아와서,

다시 불행(?)하게도 tuple2Shape역시 두개의 Shape instance를 implicit parameter로 입력받고 있다. 

(implicit u1: Shape[_ <: Level, M1, U1, P1], u2: Shape[_ <: Level, M2, U2, P2])

M1 -> Rep[Int]
U1 -> Int
M2 -> Rep[String]
U2 -> String

최초 Table class에서 정의한 개별 Column에 1:1로 대응되는 개별의 Shape 객체가 implicit parameter로 처리되고 있다. 

해당 Shape parameter의 instance(Column의 Type별로 대응되는 Shape 객체)를 생성하는 implicit는 아래와 같다.

trait RepShapeImplicits extends OptionShapeImplicits {
  /** A Shape for single-column Reps. */
  @inline implicit def repColumnShape[T : BaseTypedType, Level <: ShapeLevel] 
  	= RepShape[Level, Rep[T], T]
}

실제 return object 생성의 구현은 아래의 RepShape object 에서 처리를 하고 있다.

object RepShape extends Shape[FlatShapeLevel, Rep[_], Any, Rep[_]] {
  def apply[Level <: ShapeLevel, MP <: Rep[_], U]: Shape[Level, MP, U, MP] = this.asInstanceOf[Shape[Level, MP, U, MP]]

  /** ... **/
}

 

특이한 점은 apply method 정의에서 별도의 입력 parameter가 없는데, 실제 처리에서는 아래와 같이 type과 관련된 매개변수를 전달받는 형태라는 것이다.

repColumnShape(intColumnType), repColumnShape(stringColumnType)

repColumnShape method의 parameter는 어디에 숨어있는가? 답은 해당 method의 정의에서 찾을 수 있다.

 

repColumnShape[T : BaseTypedType, Level <: ShapeLevel] 에서 T : BaseTypedType 는 context bound(한국말, 맥락 바운드)에 해당된다.  

 

context bound이 이해를 위해 아래의 간략한 예제를 살펴보자

/** ref.scala **/
trait Stype[A]
trait FruiteImplicit {
  @inline implicit def makeFruite[A: Stype] = Apple[A, A]
}

abstract class BaseFruite[A, B] {
  def name() : String
}

object Apple extends BaseFruite {
  def apply[A, B]: BaseFruite[A, B] = this.asInstanceOf[BaseFruite[A, B]]
  override def name(): String = "Apple!"
}

/** main.scala **/
object Main extends App {
  println(makeFruite(new Stype[Int]{}).name())
  
  implicit val sTypeInt = new Stype[Int] {}
  println(makeFruite.name())
}

output:
Apple!
Apple!

implicit def makeFruite[A: Stype]는 명시적으로는 parameter가 정의가 없는 형태이다. 

 

makeFruite은 정의에서 [A: Stype]의 context bound가 사용되고 있다. 암시적으로 (implict a: Stype[A]) parameter가 정의된다.

실질적으로는 아래와 같이 처리된다.

makeFruite[A, Stype](implicit a: Stype[A])

위의 예제에서 암시적 정의부를 명시적으로 호출하면 아래와 같이 정상적인 출력 결과 Apple! 을 확인할 수 있다.

println(makeFruite(new Stype[Int]{}).name())
// Apple!

다른 특이점으로 Apple object의 apply method 상에서 parameter 정의가 없으나 실제 호출시 명시적으로 값을 설정할 수 있으며, this.asInstanceOf[BaseFruite[A, B]]를 통하여 해당 object의 super class type의 instance를 반환하고 있다. (코드 패턴으로 보임)

 

다시 본론으로 돌아와서, 

아래의 reColumnShape는 context bound 로 정의( [T: BaseTypedType,..] )로 인하여 BaseTypedType[T]의 parameter를 역시나 암시적 매개변수(implicit parameter)로 처리된다.

repColumnShape(intColumnType), repColumnShape(stringColumnType)

// 참고
trait RepShapeImplicits extends OptionShapeImplicits {
  /** A Shape for single-column Reps. */
  @inline implicit def repColumnShape[T : BaseTypedType, Level <: ShapeLevel] 
  	= RepShape[Level, Rep[T], T]
}

그러면 위의 intColumnType, StringColumnType 은 어떤식으로 implicit로 정의되어 있을까?

/** JdbcTypesComponent.scala **/
trait JdbcTypesComponent extends RelationalTypesComponent { self: JdbcProfile =>
  trait ImplicitColumnTypes extends super.ImplicitColumnTypes {
    /** ... **/
    implicit def intColumnType = columnTypes.intJdbcType
    implicit def longColumnType = columnTypes.longJdbcType
    implicit def shortColumnType = columnTypes.shortJdbcType
    implicit def stringColumnType = columnTypes.stringJdbcType
    
  }
  
  class JdbcTypes {
    val instantType = new InstantJdbcType
    val stringJdbcType = new StringJdbcType
    /** ... **/
    
    class IntJdbcType extends DriverJdbcType[Int] with NumericTypedType {
      def sqlType = java.sql.Types.INTEGER
      def setValue(v: Int, p: PreparedStatement, idx: Int) = p.setInt(idx, v)
      def getValue(r: ResultSet, idx: Int) = r.getInt(idx)
      def updateValue(v: Int, r: ResultSet, idx: Int) = r.updateInt(idx, v)
    }
    
    class StringJdbcType extends DriverJdbcType[String] {
      /** ... **/
    }
    
    /** ... **/
  }
}

위와 같이 정의되어 있다. 각 Column의 data type에 1:1로 대응되는 객체를 필요할때마다 가져다 쓰는 느낌으로 보면 될 듯하다.

Slick의 자료구조는 계층적으로 매우 복잡한데 Context Bound로 정되었던 BaseTypedType[T]는 계층구조에서 아래와 같이 상위에 위치하고 있음을 확인할 수 있다.

그림을 마지막에 첨부한 이유는,

이 분석의 시작은 provenShapeOf 가 아닌 위의 Data 계층구조부터 시작하는게 맞는게 아니었나 하는 생각이 들었기 때문이다.

무엇이든 기반부터 이해하고 시작하는게 중요하지만 결코 쉽지는 않은 것 같다.

 

분석 후기

잘 알지도 못하는 Scala를 조금은 잘해보고 싶어서 시작한 분석이 한달이 넘게 걸릴 줄은 몰랐다. 

중간중간 막히는 Scala 코드의 패턴과 숨어있는 implicit를 찾아내느라.. 

어느 스칼라 고수의 말 처럼 '스칼라는 건드리는 거 아니다' 이게 답인지도 모르겠다.

Java와는 달리 Scala는 Type의 정의에 과도하게 집착하는 모습을 볼 수 있었고,

Slick 개발자는 반복과 비효율을 싫어하는지 implicit도 아주 과다하지만 계층적으로 잘 사용하고 있음을 확인할 수 있었다.

이러한 구현은 만드는 사람은 괴롭지만 쓰는 사람의 입장에서는 극단적으로 간결하고 Readabilty가 효율적인 코드를 구사할 수 있게 해준다.

물론 간결한 코드에 의문을 가지고 그 의문을 해결하고자 한다면 implicit와 Type의 악몽에 빠질 수 있음을 명심하자.

 

많이는 없겠지만 Scala 공부를 하는 자들을 위해 위의 코드를 극단적으로 단순화시킨 코드를 첨부한다.

코드 패턴을 연습해보자.

머리로 이해하면 문제는 풀 수 있지만 손이 이해해야 창조가 가능..

/** VirtualSlick.scala **/

object Virtual {
  val types = new JdbcTypes
  implicit def intCon = types.intJdbcType
  implicit def stringCon = types.stringJdbcType

  class Hell extends Table[(Int, String)]("TB_HELL") {
    def id = column[Int]("HELL_ID")
    def firstName = column[String]("FIRST_NAME")

    def * = (id, firstName)
  }

  def main(args: Array[String]): Unit = {

  }
}



/** VirtualSlickCore.scala **/

trait JdbcTypesComponent { self: JdbcProfile =>
  trait ImplicitColumnTypes {
    implicit def intColumnType = columnTypes.intJdbcType
    implicit def stringColumnType = columnTypes.stringJdbcType
  }
}

trait JdbcProfile {
  val columnTypes = new JdbcTypes
}

class JdbcTypes {
  val intJdbcType = new IntJdbcType
  val stringJdbcType = new StringJdbcType

  class IntJdbcType extends BaseTypedType[Int]
  class StringJdbcType extends BaseTypedType[String]
}

trait ProvenShape[U] {
  def value: Any
}

object ProvenShape {
  implicit def proveShapeOf[T, U](v: T)(implicit sh: Shape[_ <: FlatShapeLevel , T, U, _]): ProvenShape[U] =
    new ProvenShape[U] {
      override def value: Any = ???
    }
}

abstract class Shape[Level <: ShapeLevel, -Mixed_, Unpacked_, Packed_] {
}

object Shape extends TupleShapeImplicits with RepShapeImplicits {
}

class TupleShape[Level <: ShapeLevel, A, B, C](val shapes: Shape[_ <: ShapeLevel, _, _, _]*) extends Shape[Level, A, B, C] {
}

trait BaseTypedType[T]
trait RepShapeImplicits {
  @inline implicit def repColumnShape[T : BaseTypedType, Level <: ShapeLevel] = RepShape[Level, Rep[T], T]
}

object RepShape extends Shape[FlatShapeLevel, Rep[_], Any, Rep[_]] {
  def apply[Level <: ShapeLevel, MP <: Rep[_], U]: Shape[Level, MP, U, MP] = this.asInstanceOf[Shape[Level, MP, U, MP]]
}

trait ShapeLevel
trait FlatShapeLevel extends ShapeLevel

trait TupleShapeImplicits {
  @inline
  implicit final def tuple2Shape[Level <: ShapeLevel, M1,M2, U1,U2, P1,P2](implicit u1: Shape[_ <: Level, M1, U1, P1], u2: Shape[_ <: Level, M2, U2, P2]): Shape[Level, (M1,M2), (U1,U2), (P1,P2)] =
    new TupleShape[Level, (M1,M2), (U1,U2), (P1,P2)](u1,u2)
  @inline
  implicit final def tuple4Shape[Level <: ShapeLevel, M1, M2, M3, M4, U1, U2, U3, U4, P1, P2, P3, P4](implicit u1: Shape[_ <: Level, M1, U1, P1], u2: Shape[_ <: Level, M2, U2, P2], u3: Shape[_ <: Level, M3, U3, P3], u4: Shape[_ <: Level, M4, U4, P4]): Shape[Level, (M1, M2, M3, M4), (U1, U2, U3, U4), (P1, P2, P3, P4)] =
    new TupleShape[Level, (M1, M2, M3, M4), (U1, U2, U3, U4), (P1, P2, P3, P4)](u1, u2, u3, u4)
}

trait Rep[T]
abstract class AbstractTable[T](val tableName: String) extends Rep[T] {
  type TableElementType = T
  def * : ProvenShape[T]
}
abstract class Table[T](tableName: String) extends AbstractTable[T](tableName) {
  def column[C](n: String) : Rep[C] = new Rep[C] {
  }
}

 

https://www.slideshare.net/jeffykim/ver-2-249475816

 

댓글 감성 분석 상용화 개발기(Ver. 2)

모 앱스토어에서 개발된 댓글의 긍정,부정,VOC 분류 자동화를 위한 연구 개발기를 정리하였습니다. 상용화 오픈 이후 1년이 진행된 시점에 작성된 문서입니다. Java 기반의 ML 툴인 DL4J가 적용되었

www.slideshare.net

 

모 앱스토어의 댓글의 긍정, 부정, VOC를 실시간으로 분류할 수 있는 서비스 개발, 상용화하면서,

공유할 만한 내용들을 정리하였습니다.

 

Java 기반의 머신러닝 툴인 DL4J를 사용하였고, 분류에는 CNN을 주기술로  Word2vec 등의 활용되었습니다.

상용서비스를 위해  Springboot 기반의  서비스 배포환경이 적용되었습니다.

산티아고 순례길, JMT(존 뮤어 트레일)과 함께 세계 3대 트레일로 불리는 캐나다 빅토리아 섬의 WCT(West Coast Trail)에 다녀온 영상을 정리하였습니다. 

 

2019년 5월에 register-in 하여 5일간 백패킹 모드로 76km를 이동하였습니다.

아래 영상은 1일 차 영상 정리분입니다.

 

 

작성중 ...

'Expired > Java Works' 카테고리의 다른 글

왜 Java NIO는 사용하기 힘든가?  (2) 2011.12.23
Java로 구현하는 웹기반 원격제어 프로그램  (8) 2009.04.19
자막(smi) 파일에서 대본 추출 프로그램  (6) 2008.11.09
NIO 강좌 - 3  (0) 2008.02.09
NIO 강좌 - 2  (0) 2008.02.09

DL4J의 Word2Vec을 활용하여 여러가지 실험을 해보고 있습니다.


DL4J 내부적으로 다차원 vector로 처리된 파일을 사람이 인지할 수 있도록 2차원으로 차원을 축소하여 csv 파일로 변환하는 tsne library를 제공하고 있습니다.


해당 tsne로 처리된 csv파일을 이미지화 하기 위해서 DL4J 내부적으로 UIServer를 제공합니다만, 아무리 노력해도 해당서버는 장황한 에러메시지를 뱉아내며 실행이 되지 않습니다.


UIServer의 문제를 해결해볼까 하다가, 그림 그리기가 그리 복잡하지 않은 듯 하여 D3.js를 활용하여, csv 파일 시각화하는 코드를 작성해 보았습니다.


아래는 어느 무협지를 학습시켜서, tsne로 2차원 csv 파일 추출후, 시각화한 화면입니다.




이해할 수 없는 부분이 더 많기는 합니다만, 중간중간 의미적으로 유사성이 높은 단어가 뭉쳐져 있는 모습을 확인 할 수 있습니다.


저같은 시각화로 인한 고민을 하시는 분들이 어딘가엔 계실듯 하여 대충 만들어진 html 파일을 공유합니다. 

csv파일 로딩 때문에 톰켓이나 was에 올려야 동작하며, 적절히 수정해서 쓰시면 될 듯 합니다.

파일처리를 위하여 csv 상단에 아래의 header가 추가되어야 합니다.

 ax,ay,word

1601.4119873046875,2464.052734375,검법 

-5537.662109375,1058.436767578125,독물 

-1954.671142578125,-3149.0380859375,탄로 

57.20676040649414,494.7344970703125,부축 

0.006148473359644413,-0.020917901769280434,시인 

0.001806588377803564,0.13746240735054016,겸허 

-0.039052389562129974,-0.013289138674736023,동료 


....



html 파일


 <html>

<head>

<title>D3 Axis Example</title>

<script src="http://d3js.org/d3.v2.js"></script>

<style type="text/css">

.axis path,

.axis line {

fill: none;

stroke: black;

shape-rendering: crispEdges;

}

.axis text {

font-family: sans-serif;

font-size: 9px;

}


</style>

    </head>

    

    <body>

    

    <script>

        var width = 1000,   // width of svg

            height = 1000,  // height of svg

            padding = 40; // space around the chart, not including labels

       

        var x_domain = [-500, 500],

        y_domain = [-500, 500];

            

        // display date format

        var  date_format = d3.time.format("%d %b");

        

        // create an svg container

        var vis = d3.select("body").

            append("svg:svg")

                .attr("width", width + padding * 2)

                .attr("height", height + padding * 2);

                

        // define the y scale  (vertical)

        var yScale = d3.scale.linear()

        .domain(y_domain) // make axis end in round number

.range([height - padding, padding]);   

            

        var xScale = d3.scale.linear()

        .domain(x_domain)    // values between for month of january

    .range([padding, width - padding]);   

        // define the y axis

        var yAxis = d3.svg.axis()

            .orient("left")

            .scale(yScale);

        

        // define the x axis

        var xAxis = d3.svg.axis()

            .orient("bottom")

            .scale(xScale);

            //.tickFormat(date_format);

            

        // draw y axis with labels and move in from the size by the amount of padding

        vis.append("g")

        .attr("class", "axis")

            .attr("transform", "translate("+ (width/2 ) +",0)")

            .call(yAxis);


        // draw x axis with labels and move to the bottom of the chart area

        vis.append("g")

            .attr("class", "xaxis axis")  // two classes, one for css formatting, one for selection below

            .attr("transform", "translate(0," + (height/2) + ")")

            .call(xAxis);

            

          vis.selectAll(".xaxis text")  // select all the text elements for the xaxis

          .attr("transform", function(d) {

             return "translate(" + this.getBBox().height*-2 + "," + this.getBBox().height + ")rotate(-45)";

         });

    

        vis.append("text")

            .attr("text-anchor", "middle")  // this makes it easy to centre the text as the transform is applied to the anchor

            .attr("transform", "translate("+ (padding/2) +","+(height/2)+")rotate(-90)")  // text is drawn off the screen top left, move down and out and rotate

            .text("Y-axis");


        vis.append("text")

            .attr("text-anchor", "middle")  // this makes it easy to centre the text as the transform is applied to the anchor

            .attr("transform", "translate("+ (width/2) +","+(height-(padding/3))+")")  // centre below axis

            .text("X-axis");

        

        var maxX = 10, 

        maxY = 10;

        var dirX = 1, 

        dirY = 1 ;

        

        // check max value

        d3.csv("visual7.csv", function(data){

        data.forEach(function(d){

        //console.log(d.ax + ' --- ' + maxX + ' === ' + (d.ax > maxX));

        if(Math.abs(d.ax) > maxX) maxX = Math.abs(d.ax);

        if(Math.abs(d.ay) > maxY) maxY = Math.abs(d.ay);

        });

        console.log('max X:Y = ' + maxX + ':' + maxY);

        // draw text

        drawText(vis) ;

        });

               

        function drawText(pannel) {

        d3.csv("visual7.csv", function(data){

            data.forEach(function(d){

              console.log('Word -> ' + d.ax + ',' + d.ay + ':' + d.word);

                        

              pannel.append("text")

          .style("font-size", "10px")

          .attr("x", ((d.ax/maxX) * (width/2) + width/2))

          .attr("y", (height/2 - (d.ay/maxY) * (height/2)))

          .attr("dy", '-10')

          .text(d.word)

          

            });

          });

        }

    </script>

    

    </body>

</html>


다차원을 2차원으로 축소하는 개념이라 왜곡이나 이해 못 할 부분들이 많은 것 같습니다.

t-sne 의 여러 옵션에 대한 이해가 있어야 아름다운 그림이 나올 듯 하네요. 


옵션에 대한 대략적인 이해는 아래 링크를 참조하면 될 듯 하네요.. (결국엔 이것저것 넣어보고 돌려보는게 답일 듯.. -_-)


http://docs.flowjo.com/v9/flowjo-v9-documentation-home/platforms/t-sne/


'Expired > Web Art' 카테고리의 다른 글

Java 물리 시뮬레이션 툴  (0) 2008.11.03
Tree Viewer  (0) 2008.11.02

[DEMO]

[동기]

모 앱스토어에서 결제시스템을 운영하면서 크고작은 많은 문제들이 발생을 하였고, 

제니퍼와 같은 기존의 서버 모니터링 툴로는 위험 상황을 감지하기 힘든 상황이 계속해서 발생하게 됩니다.



[운영 장애 사고의 유형]

  • 예측하기 힘든 트래픽
  • 자동화된 툴 혹은 인위적인 서비스 취약점 공격


[요구사항 원칙]

  • 앱스토어를 통해 배포된 앱별로 모니터링이 가능할 것
    • 배포된 앱별 사용자수와 앱의 이벤트 시점에 따라서 트래픽이 급증하는 상황이 발생
  • 특정사용자 혹은 사용자군의 모니터링이 가능할 것  
  • Velocity Check
    • 통상적인 수치를 벗어나는 상황에 대한 인지가 필요 (QA나 혹은 인지할 수 없는 오류 혹은 장애 확인 필요)
    • FDS 시스템과 연동 등을 고려 (특정 API에 대해서 일정 수준이하의 응답 시간 확보)


[고려사항 및 설계]

  • 로그 수집을 통한 실시간 혹은 대량의 데이터에 대한 MR 분석이 가능할 것
    • 별도 plugin 설치등 Live 서비스에 영향을 주지 않고 각 was 인스턴스의 로그만 수집하는 agent만 가동
    • 특정 기능에 대해서 일정수준 실시간 성을 지원하여 허용 시간 범위내에서 데이터 분석이 가능할 것
    • MR을 활용한 대량의 데이터를 분석하여 원사는 데이터를 추출 할 수 있을 것
    • 다양한 데이터 분석 결과에 대응할 수 있는 Chart를 지원할수 있을 것
  • 개발과 운영 비용의 최소화
    • IT회사가 아니기 때문에 장비 지원은 항상 최소를 고려 (ㅠㅠ)
      • 시스템 운영으로 장애 및 피해 예방을 통하여 비용확보 필요
    • 기존 업무를 하면서 남는 시간을 활용하여 개발 진행, 초기 운영까지도.. 개인 업무 Capacity 고려


[개발 stack]

  • Java 8
  • SpringBoot Web (with thymeleaf)
  • d3.js
  • Hbase 1.2


[참고 자료]

  • OpenTSDB
    • Hbase를 활용하여 시계열(time series) 데이터 처리를 위한 sheme 설계에 참고
  • D3.js
    • 다양한 정적 or 동적 차트 처리를 위해 도입



[시스템 구조]
  • LogCrawlAgent
    • 각 Live 서버상의 로그를 실시간으로 전송(http keepalive socket)
    • jar console application
  • TXD (Transaction Deamon)
    • 실시간 모듈 : API, 실시간 처리, UI 제공
    • Batch 모듈 : 앱 전체의 통계 및 Report 추출

[데이터 스키마]

  • 총 6개의 Table로 구성되며, 
    • 실시간 시계열 데이터 처리를 위한 tsTable은 아래와 같이 OpenTSDB 유사 scheme 적용 (아래)
    • 사용자 데이터의 Velocity Check의 응답 속도 보장을 위한 별도의 table에 scheme 적용
  • MR처리 효율상 특정 부분은 bit가 아닌 String, Number 등의 데이터 Type 적용

범주RowKey
구성metricId:3bytebaseTimeStamp:4bytekey1:3bytevalue1:3bytekeyN:3bytevalueN:3byte
각주정의된 metric 시단위까지의 변환값추가정보 key값추가정보 value값추가정보추가정보

범주Column
구성qualifervalue
데이터 구조time offset:4byteString or Long
각주

시단위 미만 시간값

임의 등록 가능



[Major Features]

  • 메인화면
    • 상단 Traffic Chart는 30초 주기로 갱신 (Cubism.js 적용)
    • 트래픽, 결제 상위 앱들의 랭킹을 최근 10분 및 일간 누적 기준으로 제공
    • 고 결제빈도 사용자 군 랭킹 추출

  • 실시간 로그 확인
    • 각 API별 트래픽의 갱신주기를 사용자가 정의할 수 있도록 하여 긴급 상황 대응

  • 트래픽 비교
    • 양일간 특정 트래픽의 추이 비교를 통하여, 감지되지 않은 장애상황 및 프로모션 등 이벤트 등 마케팅 분석에 활용


  • 앱별 트래픽
    • 특정 트래픽을 기준으로 앱별 & 시간대별 순위를 추출하여 차지하는 비중을 확인


  • 상위 결제 유저 
    • 자동화 툴 혹은 악의적(실험적)의도의 사용자 트래픽의 분석 확인

[개발과 운영을 하면서]

  • Hbase는 실시간 및 대량 Batch(MR)의 두마리 토끼를 잡을 수는 있으나,
    • 잘못된 Scheme 적용시 Data 증가에 따라 응답 속도가 기하급수적으로 느려질 수 있음 (당연한 이야기 이겠지만..)
    • Scheme 디자인시 철저히 데이터 실시간 요구사항에 근거해야 하며, 이외의 기능은 MR로 커버 가능
    • MR자체의 성능은 Hadoop의 RawData 직접 처리보다는 느리다 (2,3배 정도)
    • 성능 및 효율을 위해서는 Bit 단위의 data 를 기반으로 하는 scheme 적용이 필요하나,
      • Hadoop Eco 시스템 기반의 쿼리 등의 다양한 툴을 적용하기에 어려움이 있으며,
      • 경우에 따라서는 별도의 공용(범용) Query Lang혹은 API 제공이 필요할 듯 보이며,
      • 개발 자체의 어려움도 수반하니 신중을 기해야 하겠다
  • D3.js는 정말 훌륭하다
  • 부하량은,
    • Agent의 부하는 CPU사용률 1%이내로 Live 시스템에 미치는 영향이 거의 없으나,
    • TXD의 특정 쿼리의 실시간 처리를 위해 CPU사용률이 최대 20%가량 치솟는 경우도 있었음
      • 실시간 통계 처리를 위해서는 Cache등의 적용이 필요할 듯 (사용자가 늘어나면..)
    • Hbase
      • StandAlone 모드로 300tps 수준 로그 적제시에도 CPU 사용률은 최대 3%를 넘지않은 미미한 수준
  • TBD


'Expired > Hadoop Hbase Nutch2' 카테고리의 다른 글

Hbase JSON API 사용하기  (0) 2015.01.04
Nutch Cycle Step  (0) 2015.01.03
하둡 이클립스 플러그인(Hadoop1.1.0 Eclipse Plugin)  (0) 2012.11.17

+ Recent posts