マイクロサービスについて

マーベリック株式会社インフラエンジニアの丸山です。

※今回クラウド環境前提でのマイクロサービスについて記載します

マイクロサービス(アーキテクチャ)とは!

アプリケーションを分割し、小さなコンポーネント(部品)として再構成します。 マイクロサービスに対し、従来の構成はモノシリック(アーキテクチャ)といいます。

f:id:maverick-techblog:20200205150932p:plain

モノシリック(アーキテクチャ)とは

プロセス同士が固く結合しており、一枚岩(モノリス)のように単一のサービスとして 提供されます。

f:id:maverick-techblog:20200205150910p:plain

マイクロサービスのメリット/デメリット

メリット

  • 耐障害性が上がります
  • デプロイが気軽にできます
  • サーバ/ミドルウェアの管理工数が減ります
  • (クラウド環境前提ですが)スケーラビリティに優れています

デメリット

  • コンポーネントに分割する際に、かなりの工数がかかります
  • 比較的新しめの考え方なので、対応できる人材が少なめです

モノシリックのメリット/デメリット

メリット

  • 従来のやり方なので、対応できる人材が多いです

デメリット

  • 結合しているので、一部分で障害が発生すると全体に影響を受けやすいです
  • デプロイをする際も思わぬ部分が影響を受けたりもします。
    • 例えば一部機能でライブラリのバージョンを上げた場合、別の部分で影響が発生したなんてこともあります

世の中の風潮としては、マイクロサービス(or Cloud Native)に移行する流れのように見受けられますが、自社のリソースを把握し、どちらを利用する、もしくは一部を移行し併用する事を検討していきたいと思っております。

Scala 3.0/Dotty enumの紹介 (後半) Scalaにおけるenumeration/列挙体の歴史とScala 3.0 enum導入の背景

マーベリック株式会社、技術広報のリチャード 伊真岡です。前回に引き続きScala 3.0の主要機能の一つenumについて紹介します。前回はScala特有の事情ではなく、プログラミング言語を問わず、広く一般にenumeration/列挙体と呼ばれる機能がどんな場面で役に立つのかを紹介しました。

techlog.mvrck.co.jp

今回はScalaにおけるenumeration/列挙体の歴史とその前提となるCやJavaからの流れ、そしてScala 3.0であらたにenumが新機能として導入される背景を紹介します。

C言語の列挙体

まずはCでの列挙体について少し紹介します。以下はint型引数とSomeEnum型引数を受け取る関数を定義して、コンパイラがエラーを吐くかを試したものです。

#include <stdio.h>
#include <stdlib.h>

enum SomeEnum {
    ZERO, ONE, TWO
};

void printInt(int i) {
    printf("print int! %d\n", i);
} 

void printEnum(SomeEnum e) {
    printf("print enum! %d\n", e);
} 

int main() {
    printInt(0);    
    printInt(ZERO); 

    // compile error: invalid conversion from 'int' to 'SomeEnum'  
    // printEnum(1); 


    printEnum(ONE);
    return 0;
}

printInt(ZERO);とint引数が期待される箇所に、異なる型であるSomeEnum型の変数を渡すことができてしまいます。

Cのenumの使い方に関しては、int値のラベル付けとしての意味合いが強いと言えます。例えばswitchの条件分岐を記述する際にコードを読みやすくするために使えます。

#include <stdio.h>
#include <stdlib.h>

enum SomeEnum {
    ZERO, ONE, TWO
};

int main() {
    int i;
    int check = scanf("%d", &i);
    
    if(check != 1) {
        return 1; //error reading from console
    } else {
        switch(i) {
            case ZERO :
            printf("You entered ZERO = %d", i);
            break; 
            
            case ONE :
            printf("You entered ONE = %d", i);
            break; 
    
            case TWO :
            printf("You entered TWO = %d", i);
            break; 
            
            default :
            printf("You entered something else = %d", i);
        }
        
        return 0;
    }
}

つまりCのenumは前回の記事でも紹介した「コード中で可能な値を現実世界での正当な値に完全に一致させる」という意味合いは弱かったようです。このアイデアはElmのドキュメントに丁寧な説明がありますので再びリンクを貼ります。

guide.elm-lang.jp

Javaでのenum

次にJavaのEnumerationを振り返ります。JavaにもC同様enumというキーワードがあり、以下のコードでDayという型を定義できます。ちなみにJavaでは定数は大文字にする慣例があるので以下の曜日名はすべて大文字を使いました。

public enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

C同様switch文で使うことも出来ます。

class Main {
    public enum Day {
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    }

    public static void main(String[ ] args) {
        Day today = Day.THURSDAY;
        switch (today) {
            case MONDAY   :
            case TUESDAY  :
            case WEDNESDAY:
            case THURSDAY :
                System.out.println("Low energy");
                break;
            case FRIDAY :
                System.out.println("Happiest day of the week?");
                break;
            case SATURDAY:
                System.out.println("The world is yours");
                break;
            case SUNDAY:
                System.out.println("Time ticking to the hell");
                break;
        }
    }
}

また.values()というstaticメソッドが自動で付与され、定義したenumの値全てを取得できます。

for(Day d: Day.values()) {
    System.out.println(d);
}

さらにvalueOf()というメソッドで文字列から定義したenum型のインスタンスへの変換も出来ます。

Day d1 = Day.valueOf("MONDAY");
Day d2 = Day.valueOf("Monday"); //IllegalArgumentException 大文字と小文字は区別される

Javaのenumは内部ではjava.lang.Enumというクラスを継承しており、values()valueOf()といったメソッドもこのjava.lang.Enumから引き継いだものです。通常のクラスと同様、メンバやメソッドを持つクラスとして扱うことも可能です。Comparableインターフェースを実装しているので下記のようにcompareTo()メソッドで順番の比較ができます。

//1日差、compareToの左(receiver)が小さい
Day.MONDAY.compareTo(Day.TUESDAY);     //戻り値-1
Day.TUESDAY.compareTo(Day.WEDNESDAY);  //戻り値-1 
Day.WEDNESDAY.compareTo(Day.THURSDAY); //戻り値-1 
//複数日差、compareToの左(receiver)が小さい
Day.MONDAY.compareTo(Day.THURSDAY);    //戻り値-3 
Day.MONDAY.compareTo(Day.SUNDAY);      //戻り値-6 
//compareToの右(引数)が小さい
Day.TUESDAY.compareTo(Day.MONDAY);     //戻り値1
Day.SUNDAY.compareTo(Day.WEDNESDAY);   //戻り値4 

短いシンタックスenumeration/列挙体を定義でき、C言語のenumと比べても高機能なJavaのenumですが、Scala視点から見るとAlgebraic Data Typesの表現が出来ないことを物足りなく感じるかもしれません。そこを踏まえた上でいよいよScalaのenumeration/列挙体の歴史を紹介していきます。

Scala 2.x系の場合

Scala 2.x系ではenumeration/列挙体相当の機能を実現する選択肢が複数存在し、enumというそのものズバリなキーワードはScala 3.0/Dottyになるまで採用されていません。Scalaの思想としてlanguage constructが少ないことを重視したため、enumというキーワードを言語に追加することにはScalaの作成者であるOdersky教授は慎重でした。

ここからはScala 2系までのenumeration/列挙体相当の機能の実現方法を紹介します。まず1つ目はcase class/case objectを使う方法です。おそらくこの手法が現在のScalaコミュニティでは最も広く利用されていると思います。

sealed abstract class Day
case object Monday extends Day
case object Tuesday extends Day
case object Wednesday extends Day
case object Thursday extends Day
case object Friday extends Day
case object Saturday extends Day
case object Sunday extends Day

Algebraic Data Typesを表現することも出来ます。

//実際のScala 2.13 Option定義を簡略化したものです
sealed abstract class Option[+A]
final case class Some[+A](x: A) extends Option[A]
case object None extends Option[Nothing]

この手法はやや記述量が多く、例えばHaskellで同等のenumeration/列挙体やAlgebraic Data Typesを実現する場合と比べるとその差がわかります。

data DayOfWeek
    = Sunday
    | Monday
    | Tuesday
    | Wednesday
    | Thursday
    | Friday
    | Saturday
data Maybe a = Just a | Nothing

case class/objectの値が多数にのぼる場合は無視できない記述量の多さになってしまいます。しかし、必ずしも可読性が低いとは言えません。Scalaコードに目が慣れたプログラマはcase class/objectを見ると読解しやすいと感じるでしょう。

またAlgebraic Data Typesとして利用すると、以下のように「子」型になってしまいます。Scalaの標準ライブラリは可能な限り具体的な型を返却するように実装されていますが、Algebraic Data Typesとして使う場合は型推論を混乱させる場合があり不便です。

val opt1 = Some("str") //Some[String]型

これを避けるために明示的な型指定をしないといけません。

val opt2: Option[String] = Some("str") //Option[String]型

またJavaのEnumerationと比べて、valuesメソッドによって全ての値を取得することも出来ませんし、valueOf()メソッドでStringからインスタンスへの解決を行うことも出来ません。値どうしの順番づけもないので「MondayはTuesdayの前」といった表現をするには、自分でComparator等を実装する必要があります。

2つ目の手法はscala.Enumerationです。

object Day extends Enumeration {
  val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}

case class/case objectを使ったパターンと比べるとデメリットがいくつかあるため、Scalaプログラマの中には「scala.Enumerationを使うな!」という主張を行う人々もいます。しかし、後述のようにメリットも有るため適材適所で活かす余地のある手法です。

利点はJava enumと共通する部分が多くあります。少ない記述量でenumeration/列挙体を表現できる、valuesメソッドですべての値を取得、witName(s: String)メソッドでStringからの解決、順序定義が自動的になされる、といった点が挙げられます。これらはcase class/objectにはない優れた特徴です。

欠点のひとつ目は、Erasureの後はextends Enumerationしたクラスは同じ方になってしまうことです。これは以下のメソッドオーバーロードに関するエラーを見ていただければわかりやすいでしょう。

object Colours extends Enumeration {
  val Red, Amber, Green = Value
}

object Day extends Enumeration {
  val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}

object Functions {
  def f(x: Colours.Value)  = "That's a colour"
  def f(x: WeekDays.Value) = "That's a weekday"
}
double definition:
def f(x: Playground.this.Colours.Value): String at line 12 and
def f(x: Playground.this.WeekDays.Value): String at line 13
have same type after erasure: (x: Enumeration#Value)String

もう1つの欠点はパターンマッチでexhausitiveness checkが働かないことです。以下のコードはパターンマッチのcaseを網羅していませんが、コンパイラは警告を出しません。

object Day extends Enumeration {
  val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}

def g(day: Day.Value) = day match {
  //コンパイラはexhaustiveness check警告を出さない
  case Day.Monday => println("Mon!")
  case Day.Tuesday => println("Tue!")
}

//scala.MatchErrorが投げられる
g(Day.Thursday)

3つ目の方法はJavaのenumを利用する方法で、Scalaプログラムの中に.javaファイルを置いてそれを.scalaファイルからimportします。JavaとScalaを共存させるプロジェクトで、かつenumeration/列挙体をJava側で定義する必要があればこの選択肢を選ぶことになります。利点はJavaのenumそのままです。欠点はScalaプログラムの中で使う場合パターンマッチでexhaustiveness checkが効かない、Algebraic Data Typesとして使えないなどがあげられます。

4つ目としてenumeratumのような外部ライブラリを使う方法があります。enumeratumの機能について詳しくは言及しませんが、enumeration/列挙体のためにライブラリを追加するとなると、ソースコードの各所で使われる大きな依存性になるでしょう。慎重に検討した上で使うのが良いと思います。

Scala 3.0/Dotty enum

上記の選択肢の欠点を個別にみると、先ほど紹介したScalaのlanguage constructを少なく保つという思想のため、新しいenumキーワードを加えるほどの動機にはなりませんでした。しかしすべて合わせると十分な理由であるとOdersky教授は判断し、Scala 3.0でenumが導入されます。

https://github.com/lampepfl/dotty/issues/1970

In my personal opinion, when taken alone, neither of these criticisms is strong enough to warrant introducing a new language feature. But taking them together could shift the balance.

Scala 3.0 enumはcase class/objectとscala.Enumerationの利点両方をほとんど実現できます。よって今後はenumeration/列挙体の実現にはScala 3.0 enumが主流になっていくと予想されます。Odersky教授が「Scala 3.0は、よりOpinionatedになる」と述べていたように、Scala初心者にとってはデフォルトの選択肢があることは安心につながるでしょう。

https://www.scala-lang.org/blog/2018/04/19/scala-3.html

What’s new in Scala 3? become more opinionated by promoting programming idioms we found to work well

基本的なScala 3.0 enumの使い方については前回の記事をごらんください。ここでは前回の記事で紹介しなかったAlgebraic Data Typesとしての使い方を紹介します。case class/objectによる表現と比べ記述量が減りました。

enum Option[+T] {
  case Some(x: T)
  case None
}

また上記のようにOptionを定義すると、Some()の戻り値型がSome[String]ではなくOption[String]になるので、型推論を混乱させる可能性が減ります。

Option.Some("str") //Option[String]型

ここで注意点を一つ紹介します。下記のコードのようにScala 3.0 enumではcase 名前 {/*ボディ*/}の形でボディを定義できません。

enum Color {
  case Red { //これはエラー、caseはbodyを持てない
   //...
  }
}

正しくはこのようにenumのブロックの中にメソッドを定義する必要があり、caseを並べたパターンマッチ(下記の例では)をつかってenumの値ごとの処理を実装するように推奨されています。

enum Color {
  case Red, Green, Blue

  //こんなかんじ
  def doSomething(color: Color): Unit = {
    case Red => ...
    case Green => ...
    case Blue => ...
  }
}

この理由についてはコンパイラ側の都合でスコープの混乱を解決するためです。こちらのPull Requestで詳しく述べられています。 https://github.com/lampepfl/dotty/pull/4003

What's more, once we have eliminated case bodies we also have eliminated scope confusion. All that remains are the case parameters and extends clause. Arguably, if we choose an ADT decomposition of a problem it's good style to write all methods using pattern matching instead of overriding individual cases. So this removes an unnecessary choice.

まとめ

Scala 3.0で導入予定のenumについて2回に渡って紹介しました。とくにScala初心者にとっておすすめの機能として期待されているので、3.0がリリースされたら利用を検討してください。

参考文献

松岡(little_hand_s)さんを招待してDDDモデリング・ハンズオンを開催しました

マーベリック株式会社、技術広報のリチャード 伊真岡です。先日弊社に松岡さんをお呼びしてDDDモデリング・ハンズオンを開催しました。

little-hands.hatenablog.com

弊社では「エリック・エヴァンズのドメイン駆動設計」の読書会を行っています。過去には「実践ドメイン駆動設計」も読書会の課題図書として取り上げたのですが、エヴァンズ本はそれと比べて抽象度が高く、読解はなかなか挑戦しがいがあります。読書会メンバーから「実際にモデリングを体験したら理解の助けになるのではないか」という声が上がり、ハンズオン形式のワークショップを提供してくれる社外の講師を探すことになりました。

エリック・エヴァンスのドメイン駆動設計

エリック・エヴァンスのドメイン駆動設計

  • 作者:Eric Evans
  • 出版社/メーカー: 翔泳社
  • 発売日: 2013/11/20
  • メディア: Kindle版

そこでDDDに関して、ブログや雑誌寄稿、登壇などで積極的に情報発信を行っている松岡さんにお願いしたところ、快く弊社でのモデリング・ハンズオン開催を引き受けてくださいました。

www.slideshare.net

ハンズオンはおよそ2時間半、最初の30分程度は松岡さんからのスライドを使った解説です。「モデリングとは何?」「どんな順番でモデリングを進めればいいの?」という基本に立ち返って、かつ松岡さんの実体験とともに解説していただき、エヴァンズ本を読んだけでは理解に自信が持てなかった部分を確認しながら学べました。

その後はハンズオンに移ります。ホワイトボードを使って「ユースケース図」「ドメインモデル図」という2種類の図をみんなで描きながらモデリングを行いました。

f:id:maverick-techblog:20200121040035p:plain

2チームに分かれてのモデリングで、片方のチームは「通販サイトのアフィリエイトプログラム」もう一方は「社内本棚に置く本の購入申請プロセス」というドメインをそれぞれ選びました。

「通販サイトのアフィリエイトプログラム」は非常に範囲が大きいドメインなので、ワークショップで収まる内容にするため適宜制約を仮定しつつ、一方で現実に即した「ポイント付与のタイミング」「ユーザや商品とアフィリエイトポイントの紐付け」などの難しい課題に挑戦していました。

「社内本棚に置く本の購入申請プロセス」は現在社内で実際に使っている手順をもとに議論しました。社内手順はスプレッドシートを使った非常に簡単なもので、「申請→承認くらいしか手順がない」「購入申請が予算内かどうかしか条件がない」と思っていたのですが、モデリングによってこの予想は裏切られます。運用でカバーしていた複雑さがあらわになり、管理者が様々な条件を考え、複数の状態遷移が伴う業務手順であることがわかりました。世の中の「スプレッドシートで十分」と思っている社内業務や、「CRUDで表現できる」と思っているアプリケーションの多くも、運用でカバーしている部分を解き明かせば想像以上に複雑なのかもしれません。

以下でワークショップに関する参加者からの感想の一部を紹介します:

  • ユースケース図→ドメインモデル図を作成する流れはわかりやすい、具体から抽象の流れはやりやすい
  • 困ったら世界を単純化(この世界の住民は全員アフィリエイトプログラムの会員など)で完結した、ただ実際にはドメインモデルはさらに複雑になりそう
  • ドメインモデルのオブジェクトとテーブルは別という意識が持てた
  • 多重度(1対N、N対N)を意識すると思考漏れに気付けそうだとわかった
  • 「状態遷移図をモデリングの早いタイミングで使っていい」「ホワイトボードの議論をツール(PlantUMLなど)で清書する」と現場でも使えそうなガイドをいただけた
  • 経験をもとに技術的な解決策を提示してもらえたのが学びになりました:
    • 「問)ユビキタス言語は頑張っても語彙がブレそう→答)画面の表示文言で合わせる方法がある」
    • 「問)ドメインモデルを自然に反映するためにデータベース層はRDBよりNoSQLの方が良い?→答)突き詰めるとCQRSが選択肢の一つになる」

参加者からも好評だった今回のワークショップ、知識として身につけたものを手を動かして確認できる良い機会でした。松岡さんありがとうございました!弊社ではDDDを含め今後も開発チームのスキル向上を、楽しさを追い求めながら継続していきます。

第二回ビジネス&開発部門 合同勉強会を開催しました!

マーベリック株式会社、技術広報のリチャード 伊真岡です。昨年12月に第一回を開催した社内ビジネス & 開発部門合同勉強会、その第二回を1月7日に開催しました。

techlog.mvrck.co.jp

この勉強会は現在100人を超え、様々な部署に分かれている弊社の中でビジネス側と開発側のお互いの理解を深めようという意図で始めています。まずは楽しくワイワイしながらお互いの仕事内容を紹介し合いましょう、という会です。

f:id:maverick-techblog:20200110120913j:plain

f:id:maverick-techblog:20200110120955j:plain

第一回に引き続き、ビジネス側のメンバー2人がホワイトボードとともに「顧客への広告キャンペーンの提案の流れの紹介」「今提案の現場で何が求められているのか」「弊社が提案をする際どこが作業効率のボトルネックになっているのか」「業界の各参加者の知識レベル向上に伴う変化」などの話題について紹介しました。

今回寄せられた感想の一部はこちら。

  • 実際の提案の流れに沿って話してもらったのがよかった
  • 規制強化などの厳しいニュースがある業界ですが、明るい希望がある話が聞けるの大変いいです!
  • 営業さんの実際の動きや流れはなんとなくしかわからなかったので、聞けたことはよかったです!!
  • 変にオブラートに包まず、ぶっちゃけていくスタイルは個人的には好きです
  • 営業さんの仕事の流れがイメージできたこと。あとプレゼンの引き込まれ感がすごかったです!
  • マーケットの話はもっと聞いてみたくなりました!体系的に学ぶことも必要ですが、どこから入っていっていいかがわからん状態です。
  • 営業だけでなく運用の仕方など各部門毎の仕事内容を詳しく知りたい

引き続きビジネス側、開発側力を合わせて楽しさを追い求めていきます!

社内でビジネス&開発部門 合同勉強会を開催しました!

マーベリック株式会社、技術広報のリチャード 伊真岡です。本日は弊社で開催されたビジネス&開発部門 合同勉強会の様子を紹介いたします。

f:id:maverick-techblog:20191226030523p:plain

弊社は現在100人を超えるメンバーがいます。創業当初はもちろん小さな会社だったので、部門を越えた連携は容易でした。しかし人数が増えるにつれて会社として様々な活動ができるようになった一方、社内の他のチーム全てで何が行われるかを把握するのは難しくなっていきました。

開発部門では30人超のメンバーがバックエンド、フロントエンド、インフラ、機械学習、QA、コーポレートエンジニアなどの様々なチームに分かれています。さらに管理部門があり、開発と管理部門を合わせると会社の半分弱の人数を占めます。残り半分強はビジネスサイドと呼ばれ、広告代理店らしく営業、トレーディングデスク、クリエイティブ、など様々なチームと職種が存在します。

私は技術広報として開発部門に所属していますが、正直弊社のビジネスサイドがどうやって売上を上げているのか、その構成比、業界のトレンドや弊社の立ち位置などまだまだ把握できていない部分が数多くあります。一方でビジネスサイドも「もっと開発サイドの知見を活かせれば面白い取り組みがたくさんできるはず」という思いがあり「それならまずは気軽な勉強会で、開発とビジネスお互いが理解できる場をつくりましょう」ということで勉強会を開催することになりました。楽しそうな試みを思いついたら、現場の人間たちで実行まで移せるのはとてもマーベリックらしいと思います。

今回の勉強会ではまず営業から2人の人間が、弊社全体での売上構成の大まかな紹介、それからとある営業チームの様子紹介と、彼らの日々直面している課題を紹介してくれました。開発サイドからは15人ほどが参加して、開発から営業の2人へ多くの質問が飛び交う活気のある会となりました。一部感想を紹介すると:

  • 自社の利益構成や広告業界話はおもしろかった
  • 自社プロダクトと代理店販売の違いが興味深かった
  • 今回の続きとして同内容の話をもっと深く聞きたいし、違うテーマでも面白いと思う

さらに次回以降の勉強会に向けて、開発サイドからビジネスサイドへ共有できる話として:

  • Webシステムの構築やデータベースのテーブル設計など
  • 色んな方法での自動化に関しては話ができるかと思う、なにより「何も考えず自動化するとかえって悪化する」ケースについては役立つ知見を提供できそう

といった点があげられました。「Webシステムとデータベース?」と思うかもしれませんが、今回の営業側から参加したうちの一人はPythonとPHPが使えるそうで、彼は純粋にITの技術に興味があるのです。私リチャードはPythonもPHPもどちらもできないので負けています(笑)

感想にもあったように開発者からも勉強会は好評で、ビジネス側の2人も交流を楽しんでいたので、次回以降の勉強会を鋭意計画中です。

ここで話があさっての方向に飛ぶのですが、先日私の友人から「ガイアの夜明け」というテレビ番組を見るように強く勧められました。一部視聴者層から熱狂的な支持を得る、テレビ東京の看板番組です。その中でも友人曰く「メロン農家親子の回」が最高の出来だから絶対見ろ、メロンの出来も番組の出来も最高だから必ず見ろ、とのことでした。その中で出てきた「にっぽんの宝物」グランプリを主催する羽根拓也氏は「日本の地方には隠された宝物がある。少子高齢化でそれを継げないケースもあり、もったいない。売れる成功事例を作りたい」と、地方の隠された「宝物」を発掘しコラボ商品の企画を仕掛けています。

何が言いたいかというと、友人に感化されてただガイアの夜明けの話がしたかっただけなのですが、弊社の属するインターネット広告業界は競争が厳しく、規制も目まぐるしく変わり「今までと同じことを続けるだけでは生き残ることもままならない」という点は同じだと思います。そして、もともと宝物といえるような光る部分を持った人と人が、コラボレーションによって新しい価値を生む可能性がある点も似ていると思います。

現在のITの世界は、技術が細分化・高度化しすぎて、とても一人でプロダクト開発で採用する技術を隅から隅まで知り尽くすのは無理になってきていると言えます。もちろん世の中には超人的な技術者がいて、複数の分野で第一人者としての知識と開発力があり、新しい技術分野もあっという間にマスターしてしまう人がいるのは知っています。しかし会社としてそういう技術者を雇うのは困難を極めますし、そもそも数が少なすぎて彼らに頼った組織は作れないでしょう。一方でひとつの技術分野、もしくは1.5分野くらいで第一人者となれる技術者は弊社にもたくさんいると私は感じています。それはビジネスサイドでも同じで、業界に深く精通していて顧客から高い評価を受ける人間が数多くいます。

先程述べたPythonとPHPができる営業の人間は「今、営業が困っていることを、ITの力を使えば解決できるという場合も、営業だけではその可能性に気づくことさえ出来ない場合もある」と言っていました。反対に開発サイドも「営業が困っていること」自体に気づいてない場合が多くあるでしょうし、それが「気づいていない」という理由だけで実現できないのだとしたら機会損失です。

今後弊社内の勉強会を通して、「にっぽんの宝物」グランプリのように、もともと実力のある異分野の専門家たちがコラボする取り組みができれば面白そうです。この記事を読んでいる人の中でITの世界に長くいるひとは、世の中に出回っているプロダクトやサービスで、当初海の物とも山の物ともつかなかったのに、後に大きく成長していった例を数多く見てきたでしょう。もちろんプロダクトやサービスという大きな成果につながるものばかりではないでしょうが、社内でのコラボから生まれた成果がいつかマーベリックの事業になんらかの形で繋がる未来を期待しています。

以上、半分以上ガイアの夜明けの話のブログでした。

もうひとつ、友人はNHKのプロフェッショナル 仕事の流儀「疾走、あんこ道~菓子職人・小幡寿康~」も勧めていましたので、興味があれば見てください。

Scala 3.0/Dotty enumの紹介 (前半) enumeration/列挙体の利点と利用例

マーベリック株式会社、技術広報のリチャード 伊真岡です。今回はScala 3.0の主要機能の一つenumについて紹介します。

2019年12月現在Scalaの最新バージョンは2.13ですが、2020年中にScala 3.0のリリースが予定されています。正式リリースに向けてScala 3.0用コンパイラはDottyというプロジェクト名で開発されていて、多くのScala 3.0向け機能がすでにDottyから利用可能となっています。

そんなScala 3.0で導入されるenumは、Scalaの創始者であるEPFL(スイス連邦工科大学ローザンヌ校)のMartin Odersky教授が「Scala初心者に勧めたい3.0新機能ランキングの中で1位」と述べています。

enumを使いこなすことはScalaプログラマにとって、特にScala初心者にとって大きな力となるでしょう。enumに近い機能はScala 2.xでも利用可能でしたが、Scala 3.0のenumではより便利で簡潔な記述ができます。もしScala 3.0のenum「のみ」について知りたい方はDotty公式のenumのページを読んでください。この記事はenumの背景や利用例から説明するので少し回りくどくなっています。

また記事は前後半にわかれていて、前半である今回の記事はenumの利点や利用例を解説し、後後半ではenumの歴史を振り返り、なぜScala 3.0で新しくenumを実装する必要があったのかを説明します。Algebraic Data Typesに関する話題は後半の記事で触れます。

techlog.mvrck.co.jp

enumの利点

Scala 3.0のenumは昔からプログラミングの世界で利用されてたenumeration/列挙体と呼ばれる概念を改めてScalaの中で実装したものです。列挙体は歴史あるプログラミング言語であるC言語やPascalでも古くから利用可能でした。多くの言語で利用可能な列挙体、そのScala 3.0実装であるenumはどんな場面で利用するのが効果的なのでしょうか?それは変数の取りうる値を制限したいときです。

例を挙げて説明してみましょう。いまWebアプリケーションの画面があってドロップダウンリストから属性を選び、その値をバックエンドであるScalaアプリケーション側で処理する機能を考えます。ドロップダウンリストから属性を選ぶので、属性は予め決められた少数の選択肢の中から選ぶことになります。つまりドロップダウンリストの選択肢以外は属性として不正な値です。

f:id:maverick-techblog:20191209044057p:plain

選択肢のデータをString型でこのように表現できます。

Webアプリケーション上での表示 Stringで表現したシステム内部での属性値
コート ”Coats”
ジャケット ”Jackets”
ニット(セーター) ”KnitWear”
シャツ ”Shirts”
パンツ ”Pants”

しかしString型を使ってしまうと、上記以外の不正なStringが属性値として間違って使われてしまう可能性があります。

// 属性をStringで表現すると、ありとあらゆる不正なStringが可能!
“”                               //空String
“Socks”                          //靴下は売っていない
“あqwせdrftgyふじこlp;” //全くの不正String

ソースコード上のあらゆる場所でこの属性を表すString変数があらわれるたびに「このデータは不正なStringになってないだろうか?」という心配がつきまといます。そのため例えば下記のisValid(shoppingCategory)のような 形で、属性値を表すStringであるshoppingCategoryが正しい属性値か不正な値かをチェックする必要があります。

def doSomething(shoppingCategory: String, ...): Result = {
  if(isValid(shoppingCategory))
    ... //Exceptionをthrowする?
    ... //あるいはEitherのLeftを戻り値として返す?
}

このdoSomethingはshoppingCategoryが不正な値であったときにExceptionをthrowするか、あるいはEitherのLeftを戻り地として返却する実装を持つとします。するとdoSomethingが呼ばれるすべての場所でtry-catchでExceptionをハンドルするかEitherのLeftをハンドルします。これは面倒ですし、ソースコードの見た目も読みづらくなります。

f:id:maverick-techblog:20191209044934p:plain

このようにあらゆる場所で不正な値のチェックを行うのは大変です。Web APIと外部の境界、データベースとScalaアプリケーションとの境界など、境界部分のみでStringのチェックを行い、それ以外の場所では不正なStringが入り込む余地を残さないのが理想的でしょう。しかしそういった理想的な状態のソースコードを保つことは難しく、いつしか不注意なソースコードの変更で不正なStringが混入する可能性があります。いったん不正なStringを混入させてしまったら、あらゆる場所でのチェックが必要になり、チェックを行った際エラー処理まで考えなくてはなりません。

こういった時にenumを使うとプログラムの安全性がたかまり、コンパイラの助けによって不正な値を型レベルで防いでくれます。以下のように選択可能な属性値を定義しておくと、ShoppingCategory型の変数は不正な値を取ることはありえません。

// Scala 3.0 または Dotty
enum ShoppingCategory {
  case Coats, Jackets, KnitWear, Shirts, Pants
}

Web APIからの入力やデータベースからの入力はScalaアプリケーションと外部の境界になるので、チェックを行いStringやIntなどの型からScalaのenumで表す型へと変換する必要があります。しかし、チェックが必要なごく一部の処理とその他大部分のShoppingCategory型に変換されたあとの安全な処理を明確に分けることができます。

f:id:maverick-techblog:20191209044948p:plain

またScalaのenumはパターンマッチと相性がよく、以下のように書くと網羅的に場合分けを記述できます。コンパイラがexhaustiveness checkを行って場合分けの漏れを防いでくれるので、enumを使ったパターンマッチ網羅性に起因するエラーを未然に防ぐことができます。

enum ShoppingCategory {
  case Coats, Jackets, KnitWear, Shirts, Pants
}

def doSomething(category: ShoppingCategory): Unit = category match {
  case ShoppingCategory.Coats => … 
  case ShoppingCategory.Jackets => …
  case ShoppingCategory.KnitWear => …
  case ShoppingCategory.Shirts => …
  case ShoppingCategory.Pants => …
}

パターンマッチ網羅性に起因するエラーを見るために、下記のようにShoppingCategoryにSocksを加えてみましょう。オンラインショッピングストアで新しく靴下(Socks)の取り扱いを始めた想定です。

// 最後にSocksを追加
enum ShoppingCategory {
  case Coats, Jackets, KnitWear, Shirts, Pants, Socks
}

//ここでコンパイルエラー!Socksのcaseが含まれていない
def doSomething(category: ShoppingCategory): Unit = category match {
  case ShoppingCategory.Coats => …
  case ShoppingCategory.Jackets => …
  case ShoppingCategory.KnitWear => …
  case ShoppingCategory.Shirts => …
  case ShoppingCategory.Pants => …
}

このとき、上記のコードはSocksに対するcaseが含まれていないので以下のようなコンパイラWarningが表示されます。コンパイラの設定によってこれをWarningからエラーに変え、より安全にできます。

// [warn] -- [E029] Pattern Match Exhaustivity Warning: 
// [warn] 12 |  def doSomething(category: ShoppingCategory): Unit = category match {
// [warn]    |                                                      ^^^^^^^^
// [warn]    |                                    match may not be exhaustive.
// [warn]    |
// [warn]    |                                    It would fail on pattern case: Socks
// [warn] one warning found

enumの利用例

その他のenumの利用例を見てみましょう。例えばenumで次のように曜日を定義できます。

enum Day {
 case Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
}

Java標準のDayOfWeekですでに曜日の定義があるのでありがたみは薄いかもしれませんが、何らかの理由でDayOfWeekへの依存を避けたい場合、例えば階層型アーキテクチャを採用していて、ドメイン層にはすべて自前の型を使うルールがあるといった場合には利用できます。

この際enumでは自動的にordinalという0から始まるInt値が付与されるので、「月曜日は火曜日の前」といった比較を行うことができます。

val l = List(Day.Sunday, Day.Friday, Day.Tuesday, Day.Wednesday, Day.Saturday, Day.Monday, Day.Thursday)
    
println(l)
//List(Sunday, Friday, Tuesday, Wednesday, Saturday, Monday, Thursday)

println(l.sortWith{(day1, day2) => day1.ordinal < day2.ordinal})
//List(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)

さらにvaluesというメソッドも自動で付与され、当該のenumに属するすべての値をArrayにして取得できます。

Day.values.foreach(println) //Day.valuesはArray[Day]型
//Sunday
//Friday
//Monday
//Saturday
//Tuesday
//Wednesday
//Thursday

曜日だと7種類しかありませんが、もっと種類の多いもの、例えばHTTPのステータスコード200 Successや404 Not Found等をenumで表現できます。現在のScalaのWebフレームワークやライブラリではvalを多数並べて、ステータスコード自体はIntの値で表現している物が多いです。

実用上はvalとIntを使った定義で問題なく、不注意であったとしてもわざわざ不正なHTTPステータスコードのInt値を利用したコードを書く人は少ないでしょう。しかし、もしこれらのWebフレームワークやライブラリが誕生する前からScala 3.0が存在していたら、ステータスコードは型安全なenumを使って表現されていたかもしれません。

JSONのserialization, deserializationにおいてもenumを利用する機会は多いでしょう。Web APIを開発するとき、HTTPリクエストのボディにJSONを利用するとします。JSONのあるmemberが取りうる値を制限したいときjson-schemaであればこのようなschema定義をするでしょう。

{
  "type": "string",
  "enum": ["red", "amber", "green"]
}

Scala 3.0のenumを使えばScalaのコードで同様の制限を表現できます。Scalaで最もよく使われるJSONライブラリのひとつであるplay-jsonを使うとこう書けます。play-jsonの現行バージョンはまだDotty対応が完了していないので、下記のサンプルではimplicitを使っています。Dotty対応が完了したらgivenが使えるはずです。

enum Color {
  case Red, Amber, Green
}

object Color {
  //型レベルでこのパターンマッチがRed, Amber, Greenを網羅していると保証できない…
  def unapply(str: String): Option[Color] = str match {
    case "red" => Some(Red)
    case "amber" => Some(Amber)
    case "green" => Some(Green)
    case _ => None
  }
}

またunapplyメソッドのパターンマッチの右側がenumの値すべてを網羅しているか、コンパイラはチェックできないので、この記事の「enumの理論的側面」の項目で紹介しているようにテストコードと組み合わせて網羅性をチェックするとよいでしょう。

//play-jsonのDotty対応が完了すればimplicitではなくgivenを使えるはず
import scala.language.implicitConversions

implicit val readsColor: Reads[Color] = Reads[Color] {
  case JsString(str) => str match {
    case Color(color) => JsSuccess(color)
    case _ => JsError(str + " is not a valid color")
  }
  case json: JsValue => JsError(json.toString + " failed to convert to color")
}
case class Pen(
  owner: String,
  color: Color
)

implicit val reads: Reads[Pen] = (
  (JsPath \ "owner").read[String] and
  (JsPath \ "color").read[Color]
)(Pen.apply _)
val penJson = Json.obj("owner" -> "Alice", "color" -> "red")
println(penJson.validate[Pen])

こうして不正な値が入り込む可能性をWeb APIとその外部との境界部分に限定します。境界部分で上記のようなコードによってStringからenumへの変換を行ってしまえば、Web APIの内部では型安全なenumによって属性値を表現できます。

あるいはMySQLのようなリレーショナル・データベースではenum型をデータベース内で利用できます。

CREATE TABLE items (
    name VARCHAR(40),
    size ENUM('coats', 'jackets', 'knit_wear', 'shirts', 'pants')
);

このときScala側のenum ShoppingCategoryとデータベース側のenumの対応を付ければ、安全かつ自然な属性表現ができます。

enumを利用しない方がよい例

enumは万能のツールではありません。enumを使うのが適切でない例として、利用可能な値が頻繁に追加・削除される用途が挙げられます。

例えばタスク管理ツールなどのラベルや、進捗管理ツールの進捗ステージ名などにenumを使うとしましょう。これらは、それぞれのツールの利用者が様々なラベル名、進捗ステージ名を自由につけて使うことが予想されるので、ツールの利用者全体では利用可能な値の数が膨大になりますし、高頻度でそのラベル名や進捗ステージ名が更新されます。

f:id:maverick-techblog:20191209221504p:plain

こういったときはenumによってソースコード内で利用可能な値を制限するのではなく、素直にデータベース内にラベルや進捗ステージの名前を保存し、高頻度な更新に備えるのが良いでしょう。

enumの理論的側面

「enumの利点」で述べたようにenumを使うメリットは、型レベルで不正な値を防ぐことによってプログラムの安全性が上がることです。英語の情報になりますが、Quoraでもその点に触れている質問があり

”Strings (or Ints to represent certain meaning) are prone to errors”

という解説がなされています。「変数やパラメタ、あるいは戻り値に不正な値が使われる可能性」はソースコード上においてエラーの主要な原因のひとつです。先に述べたようにソースコード上のあらゆる場所で不正な値のチェックをしなければならないとしたら、それは非常に頭を悩ます問題です。

この点をうまく理論的に説明している資料として、Scalaとは別の言語ですがElmのドキュメントがあります。不正な値を型レベルで防ぐという点について、身近な例をまじえつつ集合論や濃度といった抽象度の高い概念を用いて説明しています。関数型プログラミングが好きな人にはElmのドキュメントの該当部分をよむと思考が整理されスッキリとした気分になるかもしれません。

Elmにおけるプログラミングの中で最も重要なテクニックのひとつは、コード中で可能な値を現実世界での正当な値に完全に一致させることです。これにより不正なデータの入り込む余地がなくなるため、…

また、取りうる値の範囲が型によって決まっているというのは、テストの面でも有利です。型によって取りうる値が制限されているため、そもそもテストを書かなくてよい場面も出てくるでしょう。それでもテストを書く必要がある場合以下のような単純なfor comprehensionですべてのケースを網羅できます。

enum Day {
  case Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
}  

enum ShoppingCategory {
  case Coats, Jackets, KnitWear, Shirts, Pants
}
for {
  day <- Day.values
  category <- ShoppingCategory.values
} doTest(doSomeMethod(day, category))

まとめ

今回の記事ではScala 3.0のenumを題材に、列挙体という概念の基本に立ち戻って使い方を紹介してきました。冒頭で言及したようにScala 2系でも列挙体として使うことのできる機能はあったので、この記事で紹介したことの多くはScala 2でも違った方法によって達成可能です。

そこで後半の内容である次回の記事では、Scala 3.0 enumが誕生した背景やScala 2系までで利用可能だった類似の機能との比較などを紹介します。

追記

初稿公開時:

(enumの利用例としてFinite State Machineの実装を紹介しようと思ったのですが、記事が長くなってきたので後半の記事で改めて紹介します。)

としておりましたが、Finite State Machineの実装にScala 3.0/Dotty enumを利用する効果的なソースコード例を提示できないため、紹介は取りやめます。

参考文献:

clickhouse-odbcインストールでぶつかったgccバージョン固定の壁をSCL (Software Collection)で乗り越えた手順

マーベリック株式会社、インフラエンジニアの小川です。

特に古めの枯れたOSを使ったサーバ上で新しめのミドルウェアを使いたいと思った時、OS標準のパッケージやライブラリが古くてすんなり入らない…といったケースは多々起きると思います。弊社では以前、ClickHouseというOSSのデータベース導入を試みたときに上記の問題に遭遇しました。そこで、弊社でどうやってそれを乗り越えたかを、SCL(Software Collections)の使い方とともに紹介します。

1年で数億件以上のレコードが追加される予測

弊社でClickHouse導入を決めたのはある広告系のプロダクトでした。プロダクト開発の初期段階でデータベースの検討を行っていたとき、ひとつのレポーティング系テーブルのサイズが非常に大きくなることが懸念されました。過去の別プロダクトのデータなどをもとに見積もると、1年間の運用で数億件のレコードが追加されていくとの予測でした。もしビジネスが予想以上に拡大すればレコード数の成長速度も更に上がります。困ったことにレポーティング系のテーブルなので基本的にデータが減ることはなく、時間の経過とともにレコード数は増える一方です。レポーティングは毎回その巨大なテーブルに対しクエリを走らせねばなりません。

f:id:maverick-techblog:20191202121802p:plain

ClickHouse

そこで目をつけたのがClickHouseです。ClickHouseは以下のような特徴を持ち、ClickHouseのデータ構造に適したケースでは行指向なデータベースより高い性能を期待できます。

  • 列指向(カラムナ)データベース管理システム
  • OLAP処理向きのデータ分析基盤
  • オープンソース
  • SQLクエリを使用して分析データレポートをリアルタイムで生成できる

社内で行ったベンチマークでは、テーブルの構成や検索条件でばらつきがありましたが、クエリを走らせたとき1秒あたり数千万~1億以上のレコードをスキャンできました。これで十分実用に耐えうる速度がでたのでClickHouseを本番導入することが決定し、以下のような構成で当時の弊社のEC2環境、Amazon Linux 1イメージのインスタンス群上でアプリケーションの構築を目指しました。

f:id:maverick-techblog:20191202122112p:plain

clickhouse-odbc

さて、自社開発アプリケーションからClickHouseに接続するにはいくつかの方法があります。このアプリケーションはHaskellで書かれていたため、ODBCドライバを経由してClickHouseを利用することにしました。

  • アプリから ClickHouse を扱うための ODBC ドライバ
    • Haskell 製アプリ → unixODBC → clickhouse-odbc → ClickHouse
  • RHEL7/CentOS7/Amazon Linux 2 用のドライバは .rpm が提供されているため、特に困らない(はず)
  • RHEL6/CentOS6/Amazon Linux 1 用は .rpm パッケージが提供されていない

f:id:maverick-techblog:20191202122233p:plain

さてこれでClickHouseのデータベースとドライバがあれば動くはずでしたが…。

gcc バージョン問題にぶち当たる

GitHub からソースを取得して、Amazon Linux 1環境のサーバ上でビルドを試みるも失敗してしまいました。 https://github.com/ClickHouse/clickhouse-odbc#installing-prerequisites-linux

You'll need to have installed:

- Fresh C compiler, which understands -std=c++14
- Static libraries
    - static libgcc
    - static libstdc++
    - static libodbc
- cmake >= 3

Fresh C compiler, which understands -std=c++14

理由としては要求されるビルド環境の条件が厳しかったことです。特に gcc の「C++14」が大きな壁で、当時弊社の環境でのEC2インスタンスのOSはAmazon Linux 1で、gcc は「C++11」なので、ビルドが通りませんでした。

何ということでしょう

OSのバージョンをAmazon Linux 2に上げずに上げずに何とかできないものか…? と探していたとき

ありました

SCL (Software Collection)

https://www.softwarecollections.org/

SCLは現環境を壊さずに、新しめのミドルウェアが使えるものです。 (Node.js における nvm, Python における PyEnv などに使用感が近いです)

SCL の使い方

scl enable devtoolset-7 bash

→ bash が新たに起動し、ライブラリは SCL 提供のものを使うようになります

使ってみた on Amazon Linux 1

$ cat /etc/system-release
Amazon Linux AMI release 2018.03
  • 通常時の GCC バージョン
$ g++ --version
g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-28)
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  • SCL 切り替え後
$ scl enable devtoolset-7 bash
$ g++ --version
g++ (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

これなら行けそうな気がしてきました!

clickhouse-odbc インストール手順

新しめの unixODBC をインストール

cd /opt/
wget http://www.unixodbc.org/unixODBC-2.3.7.tar.gz
tar zxvf unixODBC-2.3.7.tar.gz 
cd unixODBC-2.3.7
./configure && make && make install

Cmake, その他必要なものをインストール

yum -y install git cmake3 libtool-ltdl-devel --enablerepo=epel

CentOS SCL(Software Collections) インストール
元々のビルドツールやライブラリを壊すことなく、より新しいバージョンのツール/ライブラリを利用できる。 C++14 対応の gcc もこの中に含まれている。

cd /opt/
wget http://mirror.centos.org/centos/6/extras/x86_64/Packages/centos-release-scl-rh-2-4.el6.centos.noarch.rpm
wget http://mirror.centos.org/centos/6/extras/x86_64/Packages/centos-release-scl-7-4.el6.centos.noarch.rpm
wget http://vault.centos.org/6.7/SCL/x86_64/scl-utils/scl-utils-20120927-11.el6.centos.alt.x86_64.rpm
wget http://vault.centos.org/6.7/SCL/x86_64/scl-utils/scl-utils-build-20120927-11.el6.centos.alt.x86_64.rpm
yum -y install *.rpm
yum install -y devtoolset-7-gcc-c++ devtoolset-7-make devtoolset-7-build --enablerepo=epel
scl enable devtoolset-7 bash

clickhouse-odbc インストール

git clone --recursive https://github.com/yandex/clickhouse-odbc.git
cd clickhouse-odbc/
git checkout -b v0523 refs/tags/v1.0.0.20190523
mkdir -p build; cd build && cmake3 .. && make -j $(nproc || sysctl -n hw.ncpu || echo 4)
make install

無事ビルドが通れば、以下の場所に clickhouse-odbc の ODBC ドライバが出来上がる。

ls -la /usr/local/lib64/odbc/libclickhouseodbc.so 

ODBC への組み込み

ODBC で認識するように、ドライバの設定ファイルを用意する。 /etc/odbcinst.ini

[ClickHouse]
Driver=/usr/local/lib64/odbc/libclickhouseodbc.so

~/.odbc.ini

[ClickHouse]
Driver = /usr/local/lib64/odbc/libclickhouseodbc.so
url = https://localhost

動作確認

clickhouse-odbc ドライバが動作するか確認する。

isql -v ClickHouse

動作確認結果
成功すれば、ODBCのクライアントが起動する

# isql -v ClickHouse
+---------------------------------------+
| Connected!                            |
|                                       |
| sql-statement                         |
| help [tablename]                      |
| quit                                  |
|                                       |
+---------------------------------------+
SQL> 

失敗の場合は、 Segment Fault 等のエラーが出る

動作確認完了
確認できたら、クライアントから抜ける。

SQL> quit

まとめ

SCL について

冒頭で述べたように、ミドルウェアをインストールする際にOS標準のパッケージやライブラリが古くてすんなり入らないといったケースは、長年使い続けている環境だと起こりがちです。本来は素直にOSのバージョンを上げて最新に近いものを使うべきでしょうが、あらゆるしがらみでできないことがよくあります。特に GCC に手を加えるのは…正直避けて通りたいものですよね。

そういった時、今回利用した SCL で元々のOSライブラリに影響なく、より新しいバージョンのツールが使える手立てがあるのは本当に助かりました。多用するのは気が引けますが、いざ! どうしても! という時の選択肢として活用できれば良いかなと思います。

ClickHouse について

また、 ClickHouse はまだまだ知名度が低い感じがあるものの、テーブル単位でレプリケーションが設定できたりと、噛むほど面白そうなカラムナDBです。まだまだ運用して日が浅いですが、今後弊社内でも活躍の場が増えそうな予感がしています。興味あるかたはぜひチェックしてみて下さい。

参考文献