Upgrade to Pro — share decks privately, control downloads, hide ads and more …

技術未来とElixir「第1回:2020年の後、訪れるIT投資激減とシステム崩壊、そして救世主...

 技術未来とElixir「第1回:2020年の後、訪れるIT投資激減とシステム崩壊、そして救世主Elixir」

2020年の後に訪れるIT・SI業界の変化と、そこで発生する既存ITの課題、それらを踏まえた上でElixirを選ぶポイントについて説明した後、SIやプロダクション開発におけるElixirの適用について解説します

piacerex

April 16, 2018
Tweet

More Decks by piacerex

Other Decks in Programming

Transcript

  1. 1 プログラマ歴36年 / XPer歴18年 / 福岡 技術顧問 (3社) AIジョブカレ 福岡代表

    / enPiT (文科省 社会人IT育成) 講師 IT企業2社経営 。 森 正和 |> 。 Elixirコミュニティ「fukuoka.ex」 福岡 理学部 / IAI Fukuoka / 「通常の3倍」福岡 主催 「量子コンピュータ by Blueqat」 / 「OpenQL」 福岡代表 福岡Elixirプログラマ / 重力プログラマ my favotite technology & implements. Twitter/Qiita/Github @piacere_ex
  2. 2

  3. 福岡Elixirコミュ「fukuoka.ex」主催→採用企業増加 告知から9時間で満席、隔月定期開催 (偶数月) 次回#6は4月 昨年6月から半年 続けた結果、告知 から9時間で満席 になる人気コミュに 育ちました ①福岡Elixirプログラマ

    前回は、福岡での Elixirプロダクション 採用事例を紹介 国内・海外含め、 fukuoka.exでしか 得られない尖り具合 https://techjin.connpass.com/event/79311/
  4. 正月休み中、オープンソースを4本+α版2本リリース 量子コンピュータ シミュレータ (一昨日、福岡で登壇) Google Drive APIラッパー (狙え公式採用) bitFlyerアクセッサ (ビットコイン自動取引)

    Elixirの小粒でピリリ なユーティリティ群 データサイエンス プラットフォーム基盤 データサイエンス プラットフォーム基盤 も作ってます ①福岡Elixirプログラマ
  5. 12

  6. 27

  7. 1/11から、福岡での技術顧問、はじめました ⑤福岡 技術顧問 以下の福岡IT企業 (2社) の技術顧問に就任しました 主に、PJ推進支援と、Elixir導入・運用支援、エンジニア 意識向上・評価基準を提供 ※福岡企業で興味あれば下記に連絡を カラビナテクノロジー株式会社

    地元の中小企業のIT化から、大規模流通業の SI開発まで、手広く手掛ける、福岡ITの急先鋒 有限会社デライトシステムズ 販売管理の自社パッケージ提供と、AWS/GCP を中核とした高性能Webシステム開発に定評ある https://twitter.com/piacere_ex の「ダイレクトメッセージを送る」で「技術顧問、興味有」と
  8. fukoka.exが取材を受けた記事が4月くらいに出ます ⑨Elixirアドバイザーズ enPiT everiで事業担当の北九大 山崎先生と、私を 含む4名 (下記、赤囲み) で、福岡でElixir採用企業 100社を目指し、Elixirの無料相談窓口を開始予定 https://twitter.com/piacere_ex

    の「ダイレクトメッセージを送る」で「福岡Elixir相談有」と enぺだーし 複数言語の性能面と マルチコア活用に特化 ゆじかわ 他言語からElixirへの 転向とSI開発が専門 piacere ElixirのSIとFW開発 を得意とする経営者 ざっきー (山崎先生) Elixirに特化したVMを 作るカーネルハッカー
  9. 42 1. 2020年の後に訪れるIT・SI業界の変化 2. 既存のIT技術の何がイケてないのか? 3. Elixir≠「違う言語」、==「違うパラダイム」 4. SIやプロダクション開発におけるElixir ①

    Webアプリケーション開発 ② DB操作 ③ 外部API呼出、暗号化 ④ バッチ ⑤ APIサーバアプリケーション開発 ⑥ 監視/ログ/耐障害性 ⑦ マルチコア並列処理 ⑧ AI・ML呼出&データサイエンス 5. この1年、福岡で爆誕するElixirムーブメント 目次
  10. 45 1.2020年の後に訪れるIT・SI業界の変化 一方で、企業や都市、その他各所から取得できるデータ量は、 現在の200~500倍にも膨れ上がることが予想されます これは、主に以下の要因によるものです ① エンドユーザの趣向の広がりに伴う企業データの増加 ② IoTデバイスやセンサーのあらゆる場所への設置 ③

    衛星データ/宇宙データといった、新たなデータ活用 こうした大量データ需要に対し、既存の「クラスタ追加」「クラスタ 性能アップ」といったデータ増加に追いつけないスケーラビリティ では、多発するシステムダウンに怯えることとなるでしょう また、本来は大量データを捌けるマルチコアやメニーコアを活用 する言語を持っていない、という事実を目の当たりにするでしょう
  11. 47 2.既存のIT技術の何がイケてないのか? マトモに説明すると、100時間位かかる (笑) ので、メッチャ要約 すると、以下3点に集約されます ① 2003年に頭打ちしたシングルコア性能に依存し、マルチ コア/メニーコアが持つポテンシャルを引き出せていない ②

    「性能観点」や「耐障害性観点」、「安定運用の観点」で プログラミング言語を選定しておらず、「構文」や「入門が 容易」、「組める人が多い」、「情報が豊富」といった、「知 名度」もしくは「趣味要素」※で選定している ③ 10~20年後の未来における技術と経済の発展を想定 した上でのテクノロジーやエンジニアの立ち位置をイメージ できていない ※知名度・趣味要素は、選定上、大事なポイントではあるが、第一優先では無い
  12. 50 2 【補足】 .マルチコアを使いこなすのは難しい? ②同時メモリーアクセスすると性能劣化の懸念 仮に①がクリアされ、マルチコア向けに処理分割できたとしても、 複数コアから同じアドレス帯のメモリーを同時アクセスした場合、 キャッシュが有効に活用できず、下手すると、シングルスレッドより も性能劣化する危険性があります これは、一度更新されたメモリー内容が、変更可能

    (mutable) であることに大きな原因があります 有効な対策の1つとしては、一度更新したメモリーを変更不可 (immutable)にすることですが、オブジェクト指向言語は、状態 を保持し、変更することが一般的なプログラミングなため、 これの 実現には、多大なプログラミング統制が必要となり、困難です 再掲: Elixir入門#8より
  13. 51 2 【補足】 .マルチコアを使いこなすのは難しい? ③マルチスレッドの扱いが難しい 仮に①②がクリアされ、処理分割かつimmutableが実現できた としても、「マルチスレッド化」には、以下のような難しさがあります i. 処理 (ないしはコード)

    が複雑になりがち ⚫ シングルスレッドを前提としたプログラミング言語では、マルチスレッドはライブラリ等 で補完されるため、ダイレクトな記載ができず、スパゲティコードを作りがち ii. 状態の排他制御に気を使わないと競合が発生する ⚫ オブジェクト指向言語では、オブジェクトに状態を持つため、状態を変更する際に ロックしないと競合が発生してしまう (そして、ロック管理は複雑になりがち) iii. スレッドの起動自体にメモリー負荷がかかる ⚫ オブジェクト指向言語でのスレッド起動は、状態保持のために、スレッドのスタック やヒープが確保されるが、このメモリー使用量が大きく、スレッドを気軽に使えない iv. デバッグが非常に困難となる ⚫ 状態で挙動が変わるマルチスレッドのデバッグは、タイミング依存し、非常に困難 再掲: Elixir入門#8より
  14. 54 3.既存のIT技術を超えるElixirの技術 既存ITでイケてない以下3点を、Elixirは華麗にクリアしています ① マルチコア/メニーコアが持つポテンシャルをいとも簡単に 引き出せる (通常コードの軽微修正でマルチコア化可) ② 「性能観点」や「耐障害性観点」、「安定運用の観点」は、 ElixirとErlangVMを選んだ時点で内包されており、シス

    テムの運用観点はクリア済み ③ 10~20年どころか、80年後 (2100年) の技術と経済 の未来を予測する、IT道35年/SI道20年の技術者が、 業界の酸いも甘いも、大規模もアジャイルも噛み分けた 上で、長旅の末に到達したプログラミング言語がElixir Elixirへの入門は、「目的に合わせてプログラミング言語を選ぶ」 というプロの領域を更に超え、「プログラミングという行為が異なる パラダイムに移行している」ことを実感する第一歩となるでしょう
  15. 58 4.SIやプロダクション開発におけるElixir ここからは、具体的に、SIやプロダクション開発の中で、Elixirを 使う上での全体像として、以下の項目に分けてお伝えします ① Webアプリケーション開発 ② DB操作 ③ 外部API呼出、暗号化

    ④ バッチ ⑤ APIサーバアプリケーション開発 ⑥ 監視/ログ/耐障害性 ⑦ マルチコア並列処理 ⑧ AI・ML呼出&データサイエンス この説明に入る前に、福岡において、既にElixirをプロダクション 採用している各種企業をご紹介することで、リアリティあるイメージ として共有するところから始めたいと思います
  16. 63 4.福岡におけるElixirプロダクション採用事例 LINEのメッセージ「LEGY (LINE Event GatewaY)」は、 Erlangで書かれており、2012年以来、LINEの根幹を支える 高性能/安定性を実現しています (元々nginxだったが、共有 メモリのセグフォ乱発でErlangに移行)

    「Elixir Conf Japan 2017」の協賛企業でもあります 福岡社内では、Elixirを学習するメンバーが増えており、今年は 勉強会やイベント開催を博多で行う機会も増える見込み
  17. 71 4-①.Webアプリケーション開発 ElixirでWebアプリケーション開発する際、以下2種類の選択肢 があります ① Ruby on Railsに似た実装の「Phoenix」を使う ② 最低限のWebサーバ機能を持つ「Cowboy」を使う

    「使わない余計な機能があると気になって仕方無い」などの特殊 な理由が無い限り、ページテンプレート機能やMVCが充分整備 されたPhoenixがオススメです
  18. 72 2.Phoenixのインストール Phoenixをインストールします (要ネット接続) Phoenixプロジェクトを作成します (要ネット接続) 作成したプロジェクトでPhoenixサーバーを起動します # mix archive.install

    https://github.com/phoenixframework/archives/raw/master/phx_new.ez # mix phx.new sample --no-brunch # cd sample # iex -S mix phx.server 掲載スペースの関係で2行になっていますが、 URLも続けて入力してください PJ名には、英大文字を指定したり、 「web」という名前を付けると エラーないしはビルド途中でコケることがあります 再掲: Elixir入門#3より
  19. 76 4.WebアプリからDB操作する モデルを格納するDBを作成します DBが追加されていることを確認します # mix ecto.create # sudo -u

    postgres psql -l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges -------------+----------+----------+-------------+-------------+----------------------- sample_dev | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres + | | | | | postgres=CTc/postgres template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres + | | | | | postgres=CTc/postgres (4 rows) 再掲: Elixir入門#3より
  20. 77 4.WebアプリからDB操作する Webアプリ用のモデル生成を行います 今回は、titleとbodyの2項目を持ったモデルを作ります なお、第3~5引数は、以下で使われます ⚫ 「DbAccessor」…lib/sample/配下フォルダ名、テーブル操作用モジュールの名前 ⚫ 「Post」…上記フォルダ内に置かれるテーブル定義モジュールの名前 ⚫

    「posts」…このWebアプリを示すURLのディレクトリ名 この後、コマンド終盤に記載の、lib/sample_web/router.ex へのパス追加と、モデルのマイグレーションを行います # mix phx.gen.html DbAccessor Post posts title:string body:text … Add the resource to your browser scope in lib/sample_web/router.ex: resources "/posts", PostController Remember to update your repository by running migrations: $ mix ecto.migrate 再掲: Elixir入門#3より
  21. 78 4.WebアプリからDB操作する lib/sample_web/router.exへのパス追加を行います モデルのマイグレーションを行います # mix ecto.migrate defmodule SampleWeb.Router do

    use SampleWeb, :router … scope "/", SampleWeb do pipe_through :browser # Use the default browser stack get "/", PageController, :index resources "/posts", PostController end … end 再掲: Elixir入門#3より
  22. 79 4.WebアプリからDB操作する マイグレーションの結果、テーブル追加されるので、確認します # sudo -u postgres psql psql (9.3.18)

    Type "help" for help. postgres=# ¥c sample_dev You are now connected to database “sample_dev" as user "postgres". sample_dev=# ¥d posts Table "public.posts" Column | Type | Modifiers -------------+-----------------------------+---------------------------------------------------- id | integer | not null default nextval('posts_id_seq'::regclass) title | character varying(255) | body | text | inserted_at | timestamp without time zone | not null updated_at | timestamp without time zone | not null Indexes: "posts_pkey" PRIMARY KEY, btree (id) 再掲: Elixir入門#3より
  23. 83 4-②.バリデーションチェック mix phx.gen.html等で生成されたスキーマ用モジュールには、 必須バリデーションチェックが含まれているので、必須チェック不要 な場合は、対象を削除してください defmodule Sample.DbAccessor.Post do use

    Ecto.Schema import Ecto.Changeset alias Sample.DbAccessor.Post schema "post" do field :title, :string field :body, :string timestamps() end @doc false def changeset(%Post{} = post, attrs) do post |> cast(attrs, [:title, :body]) |> validate_required([:title, :body]) end end lib/sample/db_accessor/post.ex
  24. 87 4-②.SQLを直接実行する SQLを直接実行するには、Ecto.Adapters.SQL.query()を 使います (性能重視のselectを書きたいケース等で重宝します) 以下のようなミニモジュールを用意しておくと便利でしょう defmodule Db do def

    query( sql ) when sql != "" do { :ok, result } = Ecto.Adapters.SQL.query( Sample.Repo, sql, [] ) result end def columns( %{ columns: columns } = _result ), do: columns def rows( %{ rows: rows } = _result ), do: rows def columns_rows( result ) do result |> rows |> Enum.map( &( MapList.zip( columns( result ), &1 ) ) ) end def viewable( value ) when is_tuple( value ), do: Timex.to_datetime( value ) def viewable( value ), do: value end lib/util/db.ex Smallexを使うと、リスト同士を結合してマップとして 構成し直す、といったような処理が簡単に書ける
  25. 88 4-②.SQLを直接実行する:例① 簡単なSQL+Web程度なら、MVC無しでの構築もできます (以下は、ミニモジュール経由でSQLを実行する例です) <%= form_tag( "/", method: :post )

    %> <textarea name="sql" rows="25" cols="160"><%= @sql %></textarea><br> <input type="submit" value="実行"> </form> <% records = Db.query( @sql ) %> <table border="1"> <tr> <%= for column <- Db.columns( records ) do %> <th><%= column %></th> <% end %> </tr> <%= for record <- Db.columns_rows( records ) do %> <tr> <%= for column <- Db.columns( records ) do %> <td><%= record[ column ] |> Db.viewable %></td> <% end %> </tr> <% end %> </table> lib/sample_web/templates/page/index.html.eex
  26. 100 4-②.マスタデータ/非リアルタイムデータの扱い マスタデータ/非リアルタイムデータは、頻繁な更新が無い限り、 DBから毎回ロード不要なので、オンメモリ化 (と定期的なバッチ によるロード運用) により、性能向上が見込めます Elixirでオンメモリ化するには、以下3種類の方法があります ① プロセス

    (GenServer、Agent) のデータとして持たせる ② ETSやmnesia等、Elixir標準のオンメモリDBにロード ③ Redis等のElixir標準で無いオンメモリDBにロード このうち、最もてっとり早いのは、②のETSやmnesia等のElixir 標準のオンメモリDBを使う方法です ※なお、定期的なバッチによるロードは、後の章で解説します
  27. 107 lib/Crawl.exを作り、HTTPoisonでQiita APIの呼び出しを 行うコードを書きます 実行すると、Qiitaから取得したJSONが出力されます 前ページで見たBody部だけで無く、StatusCode等も見れます defmodule Crawl do def

    get() do HTTPoison.get!( "https://qiita.com/api/v2/items?query=Elixir" ) end end # iex –S mix iex> Crawl.get %HTTPoison.Response{body: "[{¥"rendered_body¥":¥"¥¥n¥¥u003ch2¥¥u003e¥¥n … ,¥"title¥":¥"AWS EC2にElixir/Phoenixをインストール¥",¥"updated_at¥":¥"2017-02-10T13:08:41+09:00¥", …(他記事が複数並ぶ)… ¥"twitter_screen_name¥":null,¥"website_url¥":null}}]", …(Body部以外のHeader等が複数並ぶ)… {"Content-Length", "4293"}, {"Connection", "keep-alive"}], status_code: 200} 5.JSONパースに必要なライブラリ導入 再掲: Elixir入門#1より
  28. 110 認証を伴うAPIは、APIキー/シークレットと、タイムスタンプ、 HTTPメソッド、パス、ボディを連結し、HMAC SHA-256などで 署名したものをヘッダーに付与して、API呼出を行います 4-③.暗号化 defmodule BitFlyex do defp

    domain(), do: "https://api.bitflyer.jp" defp api_key(), do: Application.get_env( :bitflyex, :setting ) |> Poison.decode! |> Map.get( "api_key" ) defp secret(), do: Application.get_env( :bitflyex, :setting ) |> Poison.decode! |> Map.get( "secret" ) defp get_private( path, map_function ¥¥ &nop/1 ) do Json.get( domain(), path, [ "ACCESS-KEY": "#{api_key()}", "ACCESS-TIMESTAMP": "#{timestamp()}", "ACCESS-SIGN": "#{sign( "GET", path )}", "Content-type": "application/json", ], map_function ) end def timestamp(), do: Dt.now_timestamp( "-", "T", ":", "." ) |> String.slice( 0, 22 ) defp sign( method, path, body ¥¥ "" ) do :crypto.hmac( :sha256, secret(), timestamp() <> method <> path <> body ) |> Base.encode16 |> String.downcase end …
  29. 119 5.JSON APIを作ってみる JSON APIも、前述したWebアプリとほぼ同じ手順で作れます まずJSON API用のPhoenixプロジェクトを作成します モデルを格納するDBを作成します JSON API用のモデル生成を行います

    (違いはjson指定のみ) 前述のWeb用のモデルと全く同じモデルで作ってみます # mix phx.new api --no-brunch # cd api # mix phx.gen.json Tool Post posts title:string body:text … Add the resource to your api scope in lib/api_web/router.ex: resources "/posts", PostController, except: [:new, :edit] Remember to update your repository by running migrations: $ mix ecto.migrate # mix ecto.create 再掲: Elixir入門#3より
  30. 120 5.JSON APIを作ってみる web/router.exへのパス追加を行います 同時に、CSRFを防止する部分も解除しておきます モデルのマイグレーションを行います # mix ecto.migrate defmodule

    ApiWeb.Router do use ApiWeb, :router pipeline :browser do … plug :fetch_flash # plug :protect_from_forgery plug :put_secure_browser_headers … scope "/", ApiWeb do pipe_through :browser # Use the default browser stack get "/", PageController, :index resources "/posts", PostController, except: [:new, :edit] end … end CSRF(cross-site request forgeries)の防止 が行われると、CSRFトークン(csrf_token)を渡さ ないとアクセスできなくなるため解除する ※ステートレスなREST APIでの本CSRF対策は実装が難しい 再掲: Elixir入門#3より
  31. 121 5.JSON APIを作ってみる マイグレーションの結果、テーブル追加されるので、確認します # sudo -u postgres psql psql

    (9.3.18) Type "help" for help. postgres=# ¥c api_dev You are now connected to database “api_dev" as user "postgres". web_dev=# ¥d posts Table "public.posts" Column | Type | Modifiers -------------+-----------------------------+---------------------------------------------------- id | integer | not null default nextval('posts_id_seq'::regclass) title | character varying(255) | body | text | inserted_at | timestamp without time zone | not null updated_at | timestamp without time zone | not null Indexes: "posts_pkey" PRIMARY KEY, btree (id) 再掲: Elixir入門#3より
  32. 122 5.JSON APIを作ってみる JSON API用のコマンドとして、どのようなものが用意されたかは、 以下で確認できます 一覧取得 (index)は、/postsへのGETで行います アイテム登録 (create)は、/postsへのPOSTで行います

    各アイテム取得 (show)は、/posts/1、/posts/2等のID指 定でのGETで取得でき、アイテム更新 (update)はID指定の PUTもしくはPATCH、アイテム削除 (delete)はID指定での DELETEで行います # mix phx.routes page_path GET / Api.PageController :index post_path GET /posts Api.PostController :index post_path GET /posts/:id Api.PostController :show post_path POST /posts Api.PostController :create post_path PATCH /posts/:id Api.PostController :update PUT /posts/:id Api.PostController :update post_path DELETE /posts/:id Api.PostController :delete 再掲: Elixir入門#3より
  33. 128 4-⑦.try…catchからどう変わるのか? 監視プログラムからの復旧戦略の代表として「プロセス再起動」が ありますが、プロセス起動が軽量なElixirだから実現が現実的に なるという、プログラミング言語の特性が大きく寄与しています (関数型言語のイミュータブルな特性も、この実現に貢献します) ・本体処理と例外処理を1つ のコードにまとめられる ※これは分離できない、という デメリットでもある

    メリット デメリット Elixir 耐障害性 例外処理 ・メモリリークを作り込みやすい ・不整合を作り込みやすい ・プロセスを再起動する設計が 考慮から漏れる可能性がある ・プロセス再起動で全てが解決 できるとは限らない ※とはいえ、プロセス再起動の 設計は例外処理でも本当 は必要 ・障害対応を本体処理から 分離できる ・メモリリークや不整合を解消 できる構造 ・プロセス再起動を予め設計 再掲: Elixir入門#6より
  34. 131 4-⑦.耐障害性のための構成と復旧戦略 スーパーバイザから、スーパーバイザを起動するコードは、以下の ようになります 通常のサーバを起動するコードと、ほぼ変わりません import Supervisor.Spec defmodule PassSubSupervisor do

    def start_link() do servers = [ worker( PassGenServer, [ 0, [ name: :server_process ] ] ) ] Supervisor.start_link( servers, strategy: :one_for_one ) end end lib/pass_sub_supervisor.ex import Supervisor.Spec defmodule PassSupervisor do def start_link() do servers = [ supervisor( PassSubSupervisor, [ 0, [ name: :ssv_process ] ] ) ] Supervisor.start_link( servers, strategy: :one_for_one ) end end lib/pass_supervisor.ex 再掲: Elixir入門#6より
  35. 132 4-⑦.耐障害性のための構成と復旧戦略 「復旧戦略」が、何種類か選べます (代表2つを紹介) ⚫ one_for_one ・・・ 1プロセス落ちたら1プロセス再起動 ⚫ one_for_all

    ・・・ 1プロセス落ちたら配下を全再起動 共通データ 保持プロセス スーパーバイザ 監視 本体処理① プロセス 監視 本体処理②-1 プロセス 監視 本体処理②用 スーパーバイザ 本体処理②-2 プロセス 監視 監視 再掲: Elixir入門#6より
  36. 141 4-⑦.Flow…マルチコア最終兵器 Flowによるデータ処理の並列化は、Enumから、赤丸部分のみ 修正を行うだけで、非常にカンタンです defmodule ElixirFlow do def run( filename

    ) do filename |> File.stream! # データクレンジング |> Flow.from_enumerable() |> Flow.map( &( String.replace( &1, ",", "¥t" ) ) ) # ①CSV→TSV |> Flow.map( &( String.replace( &1, "¥r¥n", "¥n" ) ) ) # ②CRLF→LF |> Flow.map( &( String.replace( &1, "¥"", "" ) ) ) # ③ダブルクォート外し # 集計 |> Flow.map( &( &1 |> String.split( "¥t" ) ) ) # ④タブで分割 |> Flow.map( fn [ _head | tail ] -> tail |> List.first end ) # ⑤2番目の項目を抽出 |> Flow.partition |> Flow.reduce( fn -> %{} end, fn( name, acc ) # ⑥同値の出現数を集計 -> Map.update( acc, name, 1, &( &1 + 1 ) ) end ) |> Enum.sort( &( elem( &1, 1 ) > elem( &2, 1 ) ) ) # ⑦多い順でソート end end lib/elixir_flow.ex 再掲: Elixir入門#8より
  37. 142 4-⑦.Flow…マルチコア最終兵器 実行します Flowでは、30万行の処理に、2.53秒と、Enumの6倍の性能 が出ました このようなカンタンな修正だけで、性能が数倍も改善されるFlow は、凄まじい機能だと思いませんか? # iex -S

    mix iex> ElixirFlow.run( "test_300000.csv" ) 2017-12-22 06:58:23.907000Z 2017-12-22 06:58:26.438000Z 0m2.531s [{"Muckenfuss", 13}, {"Lieurance", 13}, {"Linkovich", 13}, {"Shultis", 12}, {"Coppes", 12}, {"Gargis", 12}, {"Macey", 12}, {"Musa", 12}, {"Cefaratti", 12}, {"Brabston", 12}, {"Razor", 12}, {"Susmilch", 12}, {"Sockalosky", 12}, … 再掲: Elixir入門#8より
  38. 145 4-⑧.ElixirからKerasを呼び出す Kerasを使うPythonも当然呼び出せます ファイル保存したら、以下コマンドを実行します defmodule Pyex do def predict() do

    { :ok, py_exec } = :python.start( [ python_path: 'lib' ] ) :python.call( py_exec, :predict_sin, :predict, [] ) end end # iex –S mix iex> Pyex.predict lib/pyex.ex 再掲: Elixir入門#7より
  39. 147 4-⑧.実際のElixirコード例 注目していただきたいのは、Elixirにおけるデータ処理が、とても 簡単に記載できる点です 「データクレンジング」と「集計・統合」の本体処理は、Elixirだと 以下の通り、非常にシンプルで、直観的です # データクレンジング |> Flow.from_enumerable()

    |> Flow.map( &( String.replace( &1, ",", "¥t" ) ) ) # ①CSV→TSV |> Flow.map( &( String.replace( &1, "¥r¥n", "¥n" ) ) ) # ②CRLF→LF |> Flow.map( &( String.replace( &1, "¥"", "" ) ) ) # ③ダブルクォート外し # 集計・統合 |> Flow.map( &( &1 |> String.split( "¥t" ) ) ) # ④タブで分割 |> Flow.map( fn [ _head | tail ] -> tail |> List.first end ) # ⑤2番目の項目を抽出 |> Flow.partition |> Flow.reduce( fn -> %{} end, fn( name, acc ) # ⑥同値の出現数を集計 -> Map.update( acc, name, 1, &( &1 + 1 ) ) end ) |> Enum.sort( &( elem( &1, 1 ) > elem( &2, 1 ) ) ) # ⑦多い順でソート 再掲: Elixir入門#9より
  40. 148 4-⑧.実際のElixirコード例 一方、Javaのコードは、可読性がElixirと比べ、良くは無いため、 処理が小さいうちはメンテナンスできたとしても、複雑になるほど、 保守コストが跳ね上がっていくことが予想できます // データクレンジング // ①CSV→TSV String[]

    lines1 = new String[ lines0.length ]; for ( i = 0; i < lines0.length; i++ ) lines1[ i ] = lines0[ i ].replace( ",", "¥t" ); // ②CRLF→LF String[] lines2 = new String[ lines1.length ]; for ( i = 0; i < lines1.length; i++ ) lines2[ i ] = lines1[ i ].replace( "¥r¥n", "¥n" ); // ③ダブルクォート外し String[] lines3 = new String[ lines2.length ]; for ( i = 0; i < lines2.length; i++ ) lines3[ i ] = lines2[ i ].replace( "¥"", "" ); // 集計 // ④タブで分割 String[][] lines4 = new String[ lines3.length ][]; for ( i = 0; i < lines3.length; i++ ) lines4[ i ] = lines3[ i ].split( "¥t" ); … 1ページ目 再掲: Elixir入門#9より
  41. 149 4-⑧.実際のElixirコード例 // ⑤2番目の項目を抽出 String[] lines5 = new String[ lines4.length

    ]; for ( i = 0; i < lines4.length; i++ ) lines5[ i ] = lines4[ i ][ 1 ]; // ⑥同値の出現数を集計 LinkedHashMap< String, Integer > lines6 = new LinkedHashMap< String, Integer >(); for ( String line : lines5 ) { if ( lines6.containsKey( line ) ) { lines6.put( line, lines6.get( line ) + 1 ); } else { lines6.put( line, 1 ); } } // ⑦多い順でソート List< Entry< String, Integer > > lines7 = new ArrayList< Entry< String, Integer > >( lines6.entrySet() ); Collections.sort( lines7, new Comparator< Entry< String, Integer > >() { public int compare( Entry< String, Integer > item1, Entry< String, Integer > item2 ) { return item2.getValue().compareTo( item1.getValue() ); } } ); 2ページ目 再掲: Elixir入門#9より
  42. 150 4-⑧.実際のElixirコード例 これは、Goにおいても似たような状況です // データクレンジング // ①CSV→TSV var lines1 []string

    for i := 0; i < len( lines0 ); i++ { lines1 = append( lines1, strings.Replace( lines0[ i ], ",", "¥t", -1 ) ) } // ②CRLF→LF var lines2 []string for i := 0; i < len( lines1 ); i++ { lines2 = append( lines2, strings.Replace( lines1[ i ], "¥r¥n", "¥n", -1 ) ) } // ③ダブルクォート外し var lines3 []string for i := 0; i < len( lines2 ); i++ { lines3 = append( lines3, strings.Replace( lines2[ i ], "¥"", "", -1 ) ) } // 集計 // ④タブで分割 var lines4 [][]string for i := 0; i < len( lines3 ); i++ { lines4 = append( lines4, strings.Split( lines3[ i ], "¥t" ) ) } 1ページ目 再掲: Elixir入門#9より
  43. 151 4-⑧.実際のElixirコード例 // ⑤2番目の項目を抽出 var lines5 []string for i :=

    0; i < len( lines4 ); i++ { lines5 = append( lines5, lines4[ i ][ 1 ] ) } // ⑥同値の出現数を集計 lines6 := Agreegates{} var rejected = lines5 for { if len( rejected ) <= 0 { break } match := rejected[ 0 ] fn := func( item string, match string ) bool { return item == match } var count int count, rejected = reject( fn, rejected, match ) var family_count = Agreegate{ family: match, count: count } lines6 = append( lines6, family_count ) } // ⑦多い順でソート sort.Sort( Desc{ lines6 } ) } func reject( fn func( item string, match string ) bool, list []string, match string ) ( int, []string ) { count := make( []string, 0 ) rejected := make( []string, 0 ) for _, item := range list { if fn( item, match ) == true { count = append( count, item ) } else { rejected = append( rejected, item ) } } return len( count ), rejected } 2ページ目 再掲: Elixir入門#9より
  44. 152 4-⑧.実際のElixirコード例 Elixirならではのデータ処理に、もう一歩、踏み込んでみましょう たとえば、RDBのような、比較的データがキレイなもの以外に、 ログファイルのような「非定型データ」から、何らかのデータを抽出 して活用したい、というケースは、割と多いのでは無いと思います 実際に、ログから抽出するケースについて見ていきましょう [datetime] 2017-09-14 03:46:03.931377,

    [subtype] bot_message, [username] segment_generate@PBAT-16, [pretext] [critical] failed to generate segment at 2017-09-14 03:45:56 UTC, [fields] <<account>> [id: 108] 株式会社hogehoge <<segment>> [id: 6959] 【mail】point <<detail>> PG::UndefinedTable: ERROR: relation customers_bd_wyna8r_1 does not exist : insert into segments_bd_wyna8r_1__20170914034556(segment_id, visitor_id) select 6959, visitor_id from ( select visitors.visitor_id from visitors_bd_wyna8r_1 visitors inner join customers_bd_wyna8r_1 customers on visitors.unique_visitor_id = customers.unique_visitor_id inner join ( select unique_visitor_id, max(id) as max_id from customers_bd_wyna8r_1 where unique_visitor_id is not null and unique_visitor_id &lt;&gt; group by unique_visitor_id ) tmp_customers on customers.id = tmp_customers.max_id and customers.unique_visitor_id = tmp_customers.unique_visitor_id inner join ( select unique_visitor_id, max(first_visit_date) as max_first_visit_date from visitors_bd_wyna8r_1 where unique_visitor_id is not null and unique_visitor_id &lt;&gt; group by unique_visitor_id ) tmp_visitors on visitors.first_visit_date = tmp_visitors.max_first_visit_date and visitors.unique_visitor_id = tmp_visitors.unique_visitor_id where customers.customer_id in (select x_4608.c_148046 as customer_id from x_4608 where x_4608.c_148046 not in (select x_4608.c_148046 as customer_id from x_4608 where x_4608.c_148074 like %1% group by x_4608.c_148046) group by x_4608.c_148046 intersect select x_4608.c_148046 as customer_id from x_4608 group by x_4608.c_148046 having sum(case when 1 = 1 then x_4608.c_148069 else 0 end) &gt; 299 intersect select x_4608.c_148046 as customer_id from x_4608 where cast(x_4608.c_148065 as BIGINT) &lt;= 1497657599000 group by x_4608.c_148046) group by visitors.visitor_id ) _tmp <!channel>, [channel_id] C2T611ZP1, [channel_name] 再掲: Elixir入門#9より
  45. 153 4-⑧.実際のElixirコード例 以下Elixirコードは、特定の文字列プリフィックスを持つ行のみを まず拾い上げ、更にその行中に、特定の取得したいデータを持つ ような場合は抽出し、mapにして返す例です Regex.named_captures()だと、ログのような非定型データ でも、パターンマッチでカンタンに値を拾い出すことができます "some.log" |> File.stream!

    |> Stream.filter( &( String.contains?( &1, "[pretext] [critical] failed to generate segment" ) ) ) |> Stream.map( &( Regex.named_captures( ~r/<<account>> ¥[id: (?<accNo>.*)] (?<accName>.*) <<segment>> ¥[id: (?<segNo>.*)] (?<segName>.*) <<detail>>/, &1 ) ) ) |> Stream.map( fn( %{ "accNo" => accNo, "accName" => accName, "segNo" => segNo, "segName" => segName } ) -> "#{accNo},#{accName},#{segNo},#{segName}¥n" end ) |> Enum.to_list 再掲: Elixir入門#9より
  46. 157 5.この1年、福岡で爆誕するElixirムーブメント fukuoka.ex、取材受けます(インタビュー掲載) 福岡でのElixir 大型案件始動 福岡でブームに&Elixirを求めて移住してくる第1号が確定w 高島市長と会見 感謝状…届くか? 福岡でのElixirプロダクション 採用事例が100件突破

    300人規模のElixir イベントを福岡で開催 季節外れのAdvent Calendar でQiita Elixirカテゴリを4~6月 ジャック (新人や転職組を狙う) Elixir Advent Calendarの 本家を福岡勢で完全制圧する fukuoka.exを100人 規模で開催 @ LINE ElixirでDjango以上に お手軽でWIX並に洗練 されたモダンUI生成する エンジンと、それで作った Elixirポータルをリリース fukuoka.ex、2回目の取材 来年の展望・野望を語る 高島市長を 登壇に呼ぶ 野心を振り返る 忘年会バージョン & fukuoka.ex 世界へ打って出る Elixir移住者 インタビュー ZEAM α版リリース? (Elixir Conf間に合うかな?) fukuoka.exによる Elixir本を出版 &出版記念パーティ Udemy Elixir 講座リリース 世界レベルのElixir オープンソースをリリース