TECH PLAY

株式会社エニグモ

株式会社エニグモ の技術ブログ

240

こんにちは、22新卒で入社したエンジニアのhashinoです。 この記事は Enigmo Advent Calendar 2022 の9日目の記事です。 背景 皆さん、普段コンテナを利用していますか? エニグモ では、 Kubernetesを活用 していて、開発環境でもDockerを活用しています。 しかし、最近はWebAssemblyがコンテナを完全に置き換えるかもしれないという噂も耳にします。 既に Kubernetes では、kubelet API 互換の Krustlet が注目を集めています。kubeletは、ご存知の通りOCI準拠のコンテナを実行しますが、Krustletは、 WebAssembly System Interface ランタイムのWebAssemblyを実行するものです。 Kubernetes 上でPodの代わりにWebAssemblyを動作させることが可能になっています。 また、 Fermyon では、WebAssemblyマイクロサービスを構築するためのDeveloper ToolであるSpinを開発しております。 このような背景から、コンテナの代わりにWebAssemblyが利用される未来も想像ができるようになってきたところです。 そして、先々月末に大きなニュースが発表されました。 それが、DockerのWebAssemblyコンテナです。 https://www.docker.com/blog/docker-wasm-technical-preview/ この記事を読んだところ、Docker API を使ってWASM Runtimeでの実行が可能になるということです。 さらには、WASM Imageなる概念もあり、これにより既存のDocker Imageと似たようなエコシステムやパイプラインの活用が期待できるということです。 DockerとWASMはどのように統合されたのか 多くの読者の方が気になっている点ですが、プレビューに書いてあった内容を簡単に私なりにまとめました。 OCIアーティファクト とcontainerd-shimを活用している。 OCI準拠のWebAssemblyランタイムである WasmEdge を採用し、そのために containerd-wasm-shim を作成した。containerd-wasm-shimは、OCI アーティファクト からWASMモジュールを抽出し、WasmEdge Runtimeを使用して実行する。 containerd-shimを使用できるようにするために、 --runtime=io.containerd.wasmedge.v1 フラグによって、WASM Runtimeを宣言できるようにした。 実際に私の MacBook のDocker DesktopでWASMコンテナを作成して実行してみました。 今回は、 公式のサンプル を参考に進めました。 [デモ]実際にDocker DesktopでWASMを実行してみる 今回は、Go言語を使ってサンプルを作ります。 WASMへの コンパイル が容易なtinygoをインストールします。 brew tap tinygo-org/tools brew install tinygo そして、 Hello World するだけのGoのプログラムを用意します。 本記事では、このコードをWASMバイナリに コンパイル して、それをOCIイメージとして利用可能であることを検証します。 package main func main() { println("hello world") } では、まずWASMバイナリに コンパイル しましょう。 tinygo build -o wasm.wasm -target wasm ./main.go このコマンドで、 wasm.wasm というバイナリが出来上がります。 ここから、WASMコンテナ関連の部分に入っていきます。 先ほど作成したWASMバイナリをOCI互換のイメージにしていきます。 以下のように少し無理矢理ですが、Dockerfileを使って実現できます。 FROM scratch COPY ./wasm.wasm /wasm.wasm ENTRYPOINT [ "wasm.wasm" ] ここで利用している scratch というコンテナイメージは、Dockerコンテナを実現する上で最小限のもののみので構成された非常に軽量なコンテナイメージです。 今回は、Dockerイメージのエコシステムを活用するためにscratchの中にWASMモジュールを入れるように構成しています。 docker buildx build --platform wasi/wasm32 -t sample-wasm-container . これでOCI互換のWASMイメージを作成できるはずなのですが、 ------ > exporting to image: ------ ERROR: failed to solve: operating system is not supported このようなエラーに遭遇しました。(かなり詰まりました汗) Docker DesktopのContainerdのイメージストア機能はデフォルトではOFFのようなので、ONにする必要があります。 以下のリンクの手順通りに進めることで解決しました。 https://docs.docker.com/desktop/containerd/ もう一度以下のコマンドを実行すると成功します。 docker buildx build --platform wasi/wasm32 -t sample-wasm-container . では、出来上がったイメージを見てみましょう。 ➜ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE sample-wasm-container 0.1 1711f20d1ef5 4 minutes ago 128kB ➜ docker image inspect sample-wasm-container | grep -A 3 "Architecture" "Architecture": "wasm32", "Os": "wasi", "Size": 127948, "VirtualSize": 127948, wasm32 というArchitectureのイメージができていることが確認できます。 では、ここからこのイメージをDocker Hubに登録していきます。 docker image tag sample-wasm-container mikiko/sample-wasm-container:1.0 docker image push hashino/sample-wasm-container:1.0 ところが、以下のようなエラーが出ました。 server message: insufficient_scope: authorization failed 以下のコマンドで再度dockerにログインすることで解決できました。 docker login Docker HubのWebUIからもDockerイメージが保存されていることが確認できます。 では、実際にDocker Hubに保存したWASMイメージを実行してみましょう。 docker container run --rm --name= \ --runtime=io.containerd.wasmedge.v1 \ --platform=wasi/wasm32 \ mikiko/hello-wasm-container:0.1 hello world うまく実行することができました! まとめ 本記事では、先々月に発表されたDocker WASMコンテナを実際に実行してみるところまで進めました。 Dockerが公開したWASMコンテナを検証してみました。 これまで通りのコンテナ レジストリ を活用したビルドやデプロイに関するエコシステムを活かして、WASMを導入できることが分かりました。 ぜひ、皆さんもご検証ください!! 参考文献 https://www.publickey1.jp/blog/22/docker_desktopwebassemblywebassembly.html https://github.com/opencontainers/artifacts https://www.docker.com/blog/docker-wasm-technical-preview/ https://docs.docker.com/desktop/wasm/ https://nigelpoulton.com/webassembly-the-future-of-cloud-computing/ https://github.com/tinygo-org/tinygo https://tinygo.org/docs/guides/webassembly/ 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
斧を研ぎましょう こんにちは、エンジニアの後藤です。 BUYMA のWebアプリを作る仕事をしています。 この記事は Enigmo Advent Calendar 2022 の8日目の記事です。 この記事のゴール この記事のきっかけ どのように実現するか 新たに見つけた課題 最後に 本当に最後に この記事のゴール この記事では、 Visual Studio Code でコードを書きつつ、サクサク rspec を実行したり Java プロジェクトをビルドしたり、 lint も実行したり、ということができるようになる、というのをゴールとしています。 エディタのカスタマイズを 愛する人 、エディタとコンソールを行ったり来たりしながら開発を進めている人におすすめの記事となります。 対象のエディタは Visual Studio Code です。 この記事のきっかけ この記事を書くきっかけになったのは私が、エディタを emacs から Visual Studio Code へ乗り換えたことに起因します。 emacs で rails アプリケーションを書いていたときは、 rspec -mode という emacs 拡張をインストールすることで、 rspec ファイルとテスト対象のファイルを行き来する カーソルのある行の rspec を実行 カーソルのあるファイルの rspec を実行 といったことがキーボードから行えました。 Visual Studio Code を使い始めてから、 Rails Go to Spec という プラグイン を見つけて、 rspec とテスト対象のファイルを行き来する はできるようになったのですが、カーソルのある行の rspec を実行するという事ができませんでした。そのため、 プラグイン でも書くか(エディタのカスタマイズは大好物です)、と思っていた所に良い方法をみつけたのでこの記事を書きました。 どのように実現するか 結論から言うと、 Visual Studio Code の標準機能のみで実現できました。その方法は、プロジェクトの tasks.json にタスクを追加することと、キーボードショートカットの割当です。 Visual Studio Code には、プロジェクトの ディレクト リ内にある、 .vscode/tasks.json ファイルにタスクを記述しておくことで、 Tasks: Run Task からプロジェクトに関連したタスクを実行できる、という機能です。 この機能と、 Tasks: Rerun Last Task へキーボードショートカットに割り当てることで、実現します。 では、早速 rails プロジェクトを対象に rspec を実行する場合を見ていきましょう。以下のような .vscode/tasks.json を作成します。 { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format " version ": " 2.0.0 ", " tasks ": [ { " label ": " rspec file ", " type ": " shell ", " command ": " rspec -fd ${relativeFile} ", " group ": " test ", " problemMatcher ": [ { " owner ": " ruby ", " fileLocation ": [ " relative ", " ${workspaceFolder} " ] , " pattern ": { " regexp ": " ^rspec \\ s+(.*):( \\ d+) \\ s+# \\ s+(.*)$ ", " file ": 1 , " line ": 2 , " message ": 3 } } ] } , { " label ": " rspec here ", " type ": " shell ", " command ": " rspec -fd ${relativeFile}:${lineNumber} ", " group ": " test ", " problemMatcher ": [ { " owner ": " ruby ", " fileLocation ": [ " relative ", " ${workspaceFolder} " ] , " pattern ": { " regexp ": " ^rspec \\ s+(.*):( \\ d+) \\ s+# \\ s+(.*)$ ", " file ": 1 , " line ": 2 , " message ": 3 } } ] } ] } 作成したタスクは、以下になります。 タスク名 説明 rspec file カーソルのある rspec ファイルのテストをすべて実行する rspec here カーソルのある行の rspec のテストを実行する これで準備は整いました。 それでは、開発の流れを見ていきます。 rspec ファイルを編集していて、特定のテストを実行します。 Tasks: Run Task -> rspec here を選び、カーソルのある行の rspec のみが実行されます。 また、編集中のファイル全体の rspec を実行する場合は Tasks: Run Task -> rspec file を選択することで実行できます。 加えて、 Tasks: Rerun Last Task にキーボードショートカットを割り当てておくことで(私の場合はCommand + Shift + T)で、 rspec の作成する Tasks: Run Task -> rspec here` を実行 テストが失敗するのを確認 ソースを実装 キーボードショートカットから、 Tasks: Rerun Last Task を実行 Tasks: Run Task -> rspec here が実行される テストが成功するのを確認する Tasks: Run Task -> rspec file を実行 同じファイル内の他のテストが壊れていないことを確認 という開発ができるようになりました。 ちなみに、今回は rspec に特化したタスクを記述しましたが、 JavaScript を書いているときは、lint タスクを書く、 Java プロジェクトの場合は、ビルドタスクや JUnit タスクを書いておくことで、すべての言語で同じように開発が進められます。 めでたしめでたし。 新たに見つけた課題 と思っていたのですが、新しい課題を見つけました。 rspec here で、特定のテストを実行後、ソースファイルを編集した後に、カーソルと rspec ファイルのテスト対象行に持っていかないと、同じテストが実行できません。これは、地味に面倒くさい。できれば、 Tasks: Rerun Last Task Command のようなコマンドで、最後に実行したタスクのコマンドをそのまま実行する事ができれば、解決しそうですが、どうやらそのような機能は無いようです。 これは、機能追加の依頼をするか プラグイン を書かないと解決できなさそうです。 これは、来年の アドベントカレンダー の時にでも解決したいと思います。 最後に みなさん、斧を研いでいるでしょうか? 斧を研ぐと言っても、本当に斧を研ぐということではなく「きこりのジレンマ」のお話です。「きこりのジレンマ」というのは簡単に説明を書くと、 あるところに、忙しく木をきっているきこりがいました。彼の木を切るペースが日に日に落ちていきます。それを眺めていた人が、斧の切れが悪くなっているのでは?と感じ、斧を研いではどうか、と提案するのですが、きこりは忙しくて斧を研いでいる暇はないと答えた というお話です。 では、エンジニアにとっての斧は何でしょうか?いろいろあると思うのですが、エディタは斧の一つではないかと思います。 年1回、 アドベントカレンダー の時だけなどと言っていないで、日々斧を研ぎましょう。 Tasks: Rerun Last Task Command は他のみんなも欲しいのでは、という事を信じて、 Visual Studio Code のプロポーザルを書いてみたいと思います。近い将来。 本当に最後に 明日の記事の担当はエンジニアの橋野さんです。お楽しみに。 BUYMA を作っている、株式会社 エニグモ では、斧を研いで仕事を進めるエンジニアを求めています。ご興味のある方は以下の求人を御覧ください。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、サービスエンジニアリング本部の寺田です。 軽く自己紹介になりますが、私は SIer で SE を2年間経験したのち、現職の エニグモ には 2020/7 よりジョインしております。 普段は主に Ruby on Rails を用いた BUYMA のサーバーサイド開発をやっています。 最近興味ある事は アルゴリズム で、週末には Atcoder にちょくちょく挑戦したりしています。 ちなみに、この記事は Enigmo Advent Calendar 2022 の7 日目の記事になります! 12 月はこのように弊社のエンジニアが記事を執筆しますので、ぜひお楽しみに! 組み込みメソッドをなぜ覚える必要があるのか? 私が嫌いなものそれは暗記です...なるべく覚えるものは少なく済ませたい、そんな思いが私にとって常にあるのです。そんな私にとってプログラミングを勉強したての頃に思ったことはこれでした。 「ぶっちゃけ for と if が使えれば何でもできるんじゃないのか?」 そうなんです。たいていのプログラムは for と if を使えば表せるのです。ではなぜ膨大な量の組み込みメソッドを覚える必要があるのか?? 一つ理由となるのは、それは読みやすい(=可読性が高い)からですよね。 ただ、組み込みメソッドを使ったプログラムが誰にとっても読みやすいかと言ったら、そうでもないですよね? # 配列の合計を計算する ## inject を利用した場合 puts [ 2 , 3 , 4 , 5 ].inject { |result, item| result + item } ## for ループを利用した場合 sum = 0 [ 2 , 3 , 4 , 5 ].each do |v| sum += v end puts sum 配列の要素の合計を計算するのに、 inject というメソッドを使ってます。これは知ってる人は簡単に読めるかもしれませんが、直感的には for ループを使用したプログラムの方が読みやすい気がします。 では、組み込みメソッドを絶対使った方が良いケースとはどんな場合でしょうか? それは、 「 for や if を使って簡単に書けるプログラムより圧倒的に早い場合」 これは絶対に組み込みメソッドを使ったプログラムの方がいいですよね? ruby には優れた アルゴリズム を使用して書かれたメソッドがあらかじめ存在しています。今回の記事では4つのメソッドと、これらに使用されている アルゴリズム について紹介していこうと思います! array#sort 問題 1~100000 までの数字がランダムに並び替えられた配列を、 昇順に並び替えてください。 for と if を使ったプログラム len = array.length len.times do |i| (len - 1 - i).times do |j| if array[j] > array[j + 1 ] array[j], array[j + 1 ] = array[j + 1 ], array[j] end end end for と if だけを使って簡単にプログラムを作成した場合、こんな感じでしょうか? ここで用いられている アルゴリズム は バブルソート というものです。 バブルソート の アルゴリズム を図解すると... 隣り合った数値同士を比較し、大小関係が合っていない場合は値を交換する。というのが基本の考え方になります。 1番目の要素と2番目の要素の大小関係を比較 2番目の要素と3番目の要素の大小関係を比較 ... 最後から一つ前と最後の要素の大小関係を比較 とすることで、一番最後の要素の値が確定します。 これを要素の数だけ繰り返すというものになります。 なので、ループの回数は要素の個数を n とすると、 n(n-1)/2 回で、計算量は となります。 オーダー記法 ここでオーダー記法(例に出てきた という書き方)を軽く押さえておきましょう。 オーダー記法はコンピューターによる計算がどれくらいの時間がかかるかを示したものです。 各計算量オーダーに対して、実際にどれくらいの計算回数になるのか表で見てみましょう。 10 3 33 100 100 7 664 10000 1000 10 9966 1000000 10000 13 132877 100000000 1000000 20 19931569 1000000000000 計算量はオーダーによってかなり異なることがわかります。 に比べて はかなり高速ですし、 よりは の方がとても高速です。 また、この差は数が大きくなればなるほど広がっていきます。 Ruby の組み込み関数を使用 ここで本題に戻りましょう。 Ruby の組み込みメソッドを使用して、この問題を解くならば、 Array#sort が使えます。 array.sort Array#sort で使われている アルゴリズム は クイックソート というものです。 手順としては以下の流れでソートを行います。 基準となる数値を決める 基準より小さい or 以上のグループに配列の要素を振り分けていく 要素が1つ以上あるグループにおいて、各グループ内で新たに基準を決める グループ内で基準より小さい or 以上のグループに配列の要素を振り分けていく この繰り返しを、それぞれのグループの要素が1つずつになるまで繰り返していき、最後に全てを結合することでソートを実現していきます。 計算量ですが、平均で になります。 最良のケースでは一度のループで各グループの要 素数 が、前回のループの半分の数になっていきます。 証明は省きますが、このように段々と 1/2 になっていき、最終的にn 個 => 1 個に要素が減っていくまでにかかる計算量は となると覚えておくと良いでしょう。 さらに、毎回のループで配列の長さである n 回の比較を行うため、 が計算量となります。 実際にプログラムを実行して計算量を比較してみたのが以下の実行結果です。 n = 100000 の場合、for ループによる バブルソート より、 Array.sort が 1000 倍も早いです。 Enumerable#bsearch 問題 1000000000 個の要素があるソート済みの配列の中から、 特定の数以上の値が初めて現れるインデックス番号を求めてください。 for と if を使ったプログラム array.each_with_index do |v, i| if (v >= 99999999 ) puts i break end end 非常にシンプルに考えるとこうでしょうか? このような アルゴリズム は 全探索 と呼ばれるものです。 配列の全要素に対して一つ一つ比較して答えを求めるものになります。 配列の長さだけループが走るので、長さ n の配列に対して計算量は です。 Ruby の組み込み関数を使用 では Ruby の組み込み変数、 Enumerable#bsearch を使った場合はどのような アルゴリズム になるでしょうか? array.bsearch { |x| x >= 99999999 } こちらで使用されている アルゴリズム は 二分探索 というものです。 配列の中から 2 という数字を探す場合を考えてみましょう。 こちらの アルゴリズム では以下の手順で答えを求めます。 左端と右端の値の中心の値を基準に、探したい数が基準より小さい or 以上かを求める 基準より小さい場合 => 探したい値は左半分にあるとわかる 基準以上の場合 => 探したい値は右半分にあるとわかる 要素が一つになるまで繰り返す こちらもループを繰り返すたびに、要素の数は 1/2 => 1/4 => 1/8 ... とどんどん半分になっていきます。 なので、要素が1つになるまでに必要なループのオーダーは になります。 これは全探索の よりかなり高速です。実際にプログラムを実行した結果を見てみましょう。 ものすごい違いですね。二分探索は全探索より 1000000 倍も早いという結果となりました。 Integer#pow 問題 を計算してください。 とても大きな数で累乗の計算をしてくださいというケースですね。 for と if を使ったプログラム ans = 1 ( 100000000 ).times do ans = ans * 3 end puts ans 累乗は言い換えれば底を指数の回数だけ掛け合わせるということです。 これを素直に実装すれば上記のようになるでしょうか。 指数の回数だけループして掛け算を行いますので、指数を n とすると計算量は になります。 Ruby の組み込み関数を使用 Ruby の組み込み関数には Integer#pow というものがあります。これは Integer#** と アルゴリズム は同じです。 3 .pow( 100000000 ) こちらは アルゴリズム として 繰り返し二乗法 が使われています。 例として を考えてみましょう。 愚直に計算するとループの回数は指数の数である 8 回です。 しかし は変換してみると、以下のようになることがわかります。 このように考えれば、ループの回数は4回で済みます。 この手順を一般化して考えると、以下のようになります。 求めたい値を、指数を 1/2 にした値を掛け合わせた式で表す 指数が 1 になるまで繰り返す 指数はループを経ることに 1/2 => 1/4 => 1/8 ... と半分になっていきます。指数が n ならば、1 になるまでに発生するループの回数は で非常に高速です。 実際にプログラムを動かして比較してみました。 非常に高速です。愚直な計算方法より 1000000 倍も早いことがわかります。 Integer#gcd 問題 463836510 と 692647128 の最大公約数を求めてください for と if を使ったプログラム ans = 0 ( 1 ...[a, b].max).each do |d| ans = d if (a % d == 0 && b % d == 0 ) end puts ans 最大公約数とは、二つの値を割り切れる最大の整数のことです。 この定義をそのまま実装するならば、上記のようなプログラムになるかと思います。 こちらは全探索の アルゴリズム となっていて、与えられた数が n とすると計算量は となります。 Ruby の組み込み関数を使用 最大公約数を求めるメソッドとして、 Integer#gcd が用意されています。 a.gcd(b) こちらは ユークリッドの互除法 と呼ばれる アルゴリズム が使用されています。 ユークリッドの互除法 というのは以下の定義を指します。 2つの 自然数 a, b について、a / b = q、a % b = r とすると、 「a, b の最大公約数」は「b と r の最大公約数」に等しい。 式で表すと以下のようになります。 ここで は a と b の最大公約数を表します。 これを繰り返すことによって、少ないループの回数で最大公約数を求めることができます。 例えば、629 と 481 の最大公約数を求めてみましょう。 最後のステップでは、148 を 37 で割った際のあまりは 0 になっています。 これは 148 と 37 では 37 が最大公約数になっていることを意味します。 ゆえに、 = = 37 と最大公約数が求まります。 そして、この アルゴリズム の計算量はざっくりと になります。 、 として、 より以下のことが言えます。 すなわち、 が成り立ちます。 2回のループを経ることで、確実に値が 1/2 以下になるわけですね。 先ほどから何度も出てきたパターンですが、値が 1/2 に減少していく アルゴリズム は計算量が となります。 実際に実行速度を比べてみましょう。 計算速度は 10000000 倍に速くなっています。 まとめ Ruby には良い アルゴリズム を使った組み込みメソッドが用意されている アルゴリズム の紹介として、 クイックソート 二分探索 繰り返し二乗法 ユークリッドの互除法 を紹介した。 良い アルゴリズム を使うことでプログラムは何万倍にも高速になることがある タイトル詐欺になってしまいますが、大事なことは Ruby のメソッドを覚えることではなく、どういう アルゴリズム があり、自分が直面した問題を解決するためにどの アルゴリズム を使うべきかを判断できるようになることだと思います。 Web エンジニアの裾野が広がってコンピューターサイエンスを学んだことのないエンジニアの方もいらっしゃるかと思います。この機会にぜひ興味を持ってもらえると嬉しいかなと思います! 明日の記事の担当は サービスエンジニアリング本部 の 後藤 さんです。お楽しみに! 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、2022年に新卒入社したエンジニアの川本と橋野です! この記事は Enigmo Advent Calendar 2022 の6日目の記事です。 エニグモ では社内の若手を中心にjunior workshopという名で勉強会を行っております。 経験の浅いメンバーの技術力アップを主目的としておりますが、興味のある方はどなたでも参加できる会となっています。(ベテランの方大歓迎です!) 勉強会の形式と、半年ほど勉強会をやってみて感じた、よかった点、今後やってみたいことなどを紹介できればと思います! よかった点 よかった点1:知見の共有 1つの技術書を読むのにも、1人で読むより、勉強会であれば様々な知見を持ったメンバーが集まっているので吸収できることが多いです。 技術書の内容を業務で実践したことがある人からは、具体的なエピソードを聞くことができますし、抽象的な概念の勉強をしている時は特に理解の助けになります。 よかった点2:社内交流が増える 勉強会は誰でも参加OKとしているので、様々なメンバーと交流できるいい機会となっています。 普段一緒に仕事をしないメンバーのことを知れたり、他部署の業務内容を知れたりと、コミュニケーション活性化や、会社理解の向上に繋がっていると思います。 以下のような形でSlackで勉強会当日にリマインドをしており、誰でも飛び入りでzoomのURLから参加可能となっており、参加の敷居はなるべく下げるようにしています。↓ エンジニア採用をやっている人事の方も参加してくれています! よかった点3:社内文化の形成 エニグモ には技術的関心が高いエンジニアが多いので、社内では下記のような技術力向上に向けての取り組みがされています。 Hacker's Delight エンジニアが個人的に学習していることや、業務で実践したことなどを発表するLT会です。週に1回開催しています! 開発合宿 コロナ以前は開発合宿も行われておりました!そろそろ再開できそう。。。? tech.enigmo.co.jp Ruby Kaigi 2022年は現地の 三重県 でオンフライン参加された方もいます! tech.enigmo.co.jp Kaigi on Rails 2022年は Gold Sponsors として参加しました! tech.enigmo.co.jp 若手勉強会もこうした社内文化の1つとして エニグモ に根付き、エンジニア組織全体のコミュニケーション活性化、技術力の向上に繋がると考えています。 勉強会の形式 輪講 という形式で、週替わりで担当者が勉強した内容をまとめてきて発表し、みんなが質問して議論していくというやり方でやっています。 22新卒が中心に開催している勉強会は、5月から開催しています。 そして、12月現在、2テーマ目に突入しています。 1テーマ目と2テーマ目の間には、振り返りを設けて、進め方を少し改善をしました。 初期から大切にしていることと、改善したことを紹介します。 初期から大切にしていること 勉強会を開催するにあたって、やりたいこと/やりたくないことなど開始前に話し合いました。 参加者が参加負担にならないようにし、勉強会を継続して開催したい。 受け身になりすぎないようにしたい。 この2つの意見を受けて、 みんなが予習必要ではない方式にする。 担当者は必ずしもまとめ記事を作ってくる必要はなく、最低限ファシリテートできるようにしておく。 開催出来なさそうであればその週はスキップをする。 以上のことを決め、無事に1テーマ目を終えることができました。 改善したこと 初期から大切にしていることに加え、改善した点があります。 以前は、勉強会の時間内に決めたところまでを読み終わらなかった場合、時間を延長してまでやっていました。 しかし、今は時間のキリのいいところで終わり、終わらなかった分は翌週にまわしています。 これは、1テーマ目の勉強会での反省にあった、「進度が早く、理解があまり出来ないまま進んでしまった。」という意見があったためです。 このため、現在のやり方では、みんなで疑問点を解消しつつ、意見交換をし、私たちのペースで進め、理解を確実にしていくことを大事にしています。 今後やりたいこと これまでの勉強会は技術書を読んできて、内容を議論するといった形式でしたが、もう少し手を動かしたいという意見もあります。 なので今後はライブコーディングをしながら下記のような方法も取り入れてみたいと考えています! AtCoder やLeetCodeの問題を解く。 SQL の問題を解く。 リファクタリング をみんなで考えて議論しながらやっていく。 まとめ 最近の勉強会は、担当者ごとに発表のやり方の個性がでていて、飽きず面白いです。 また、勉強会は個人にとっても会社にとってもメリットが多いと感じました。 これからも勉強会をさらに盛り上げれるように工夫していきたいと思います。 テックブログでも若手勉強会でやった内容をどんどん発信していければいいなと思っております! 明日の記事の担当は 私(川本)のメンターをしていただいているエンジニアの寺田さんです。 アルゴリズム についての記事です。お楽しみに! 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
この記事は Enigmo Advent Calendar 2022 の 5日目の記事です。 こんにちは。フロントエンドエンジニアのWooです。 エニグモ へ入社して3年目、主に BUYMA の購入者側のページをReactで構築しています。 BUYMA ではReactのグロー バルス テート管理のために主にReduxを使用していますが、今回は新しい取組としてRecoilを導入し、開発を行なってみましたので、その経験を共有しようと思います。 まず、軽くRecoilについて理解し、Recoilを導入するようになった理由、Recoilで開発する時に良かった点や困ったこと、他にリリースする際に失敗した話、最後にはこれからRecoilのより良い開発経験のための整理などをこの記事でお話ししたいと思います。 Recoilを軽く理解する RecoilはReactプロジェクトのための数多いグロー バルス テート管理ライブラリの中の一つで Facebook が2020年5月に出したライブラリです。なので、Reactを作った Facebook が出したRecoilは他のライブラリ(Redux, Mobx)とは違い、React専用ながらReactに最適化されたと言えるライブラリです。 Recoilを導入するようになった理由 2022年2月、 BUYMA では スマホ ウェブの検索絞り込み画面の使用性を高める企画が始まりました。 従来の画面はかなり前に作られたものだったので、画面の機能改善ではなく、新しい検索絞り込み画面を開発する方向に進められました。 私は新しい検索絞り込み画面のステートをどう構成すれば開発しやすくなるのかを悩みながらステート設計方法などを探していました。 そんな中、私はRecoilに接するようになりました。 新しく作る検索絞り込み画面は機能は多いながらも操作性は単純で軽い画面になる必要がありました。 Recoilはユーザーの絞り込み条件により、変更される数多いステートを柔軟に拡張・分解しながら開発ができそうと思いました。 さらにReduxに比べコードの量も多く減らすこともでき、少人数でもスピード感ある開発ができそうと思いましたので、検索絞り込み画面開発に適合だと判断、導入を進めるようになりました。 Recoilで開発した時の良かった点 まず、RecoilはReactのSuspenseと一緒に動作するようにデザインされていました。 コンポーネント をSuspenseで囲むと非同期処理などでまだ保留中の下位 コンポーネント をキャッチし、代替するUIをレンダーしてくれました。 これにより、データを取得している間のローダー表示を全体的に統一することができました。 その他、ステートを使う時にどんなエラーが生じるかの定義が簡単でした。それはErrorBoundaryでキャッチする仕組みでした。 ErrorBoundaryはエラーをキャッチし、エラーを記録し、クラッシュした コンポーネント ツリーの代わりにフォールバック用のUIを表示するReact コンポーネント でした。 グロー バルス テートの設定と定義が非常に簡単で、ステートの使用はRecoilが提供するHooksを利用、ステートをget/setすることだけでした。React Hooksの文法と似ていることで、初めてRecoilを書くエンジニアでもすぐになれる利点がありました。 また、グロー バルス テートを使用するためのボイラープレートの量が非常に少なく、全体的にラーニングカーブが低いというメリットがありました。 そして、ステートの変更や定義が頻繁に行われても既存コードとの影響度が低いため、開発要件によるステートの変更でも柔軟に対応が可能でした。 propsを渡さなくても良い特徴では コンポーネント の リファクタリング や分割などが容易でした。 API の非同期の処理ではユニークなインプットがある時のみ実行されるようにキャッシュされる処理があり、ユーザーの同じアクションを防ぐなどの実装を考慮しなくてもよい便利さがありました。 Recoilで開発をする時に困った経験 ユーザの絞り込み条件変更によるReactの処理では、Suspenseを利用しました。 Suspenseは API の非同期処理を待機中の下位 コンポーネント の代わりにローダーをレンダーしてくれましたが、実際に動作を確認すると絞り込み条件変更の度に表示される真っ白のローダー画面が与える印象は求めている操作性の軽い画面とは違うように感じられました。 非同期データを使う最小限の コンポーネント をSuspenseで囲む コンポーネント 構造が設計可能であれば、全画面ローダーを表示する必要はないかもしれません。 しかし、検索絞り込み画面はヘッダー以外のところが非同期データによってレンダーされる コンポーネント のため、前述の方法の設計ができませんでした。 そうして考えた方法は、単なるスピナー表示ではなく、スピナーを絞り込み項目と一緒に見せる方法でした。 スピナーに絞り込み項目も含めSuspenseに渡すことになったので、このローダー コンポーネント は肥大化されましたが、パフォーマンスに大きな影響はありませんでした。各 コンポーネント ごとにステートを使っているおかげで、 リファクタリング はJSX部分の簡単な修正で済みました。 Recoilで開発したコードをリリースする際に失敗した話 BUYMA のReactコードは、babelを通してES5コードに変形した後、もう一度uglifyを行う過程を経ることになります。 このES5に変形する過程ではnode_modulesを含みません。 問題はnode_modulesにあるRecoilライブラリを OOO _app.jsの結合ファイルに含めてuglifyしようとした時に起きました。 uglifyはES6コードを解析できず、圧縮に失敗していました。 Recoilライブラリは主にES6で書かれていたのが原因でした。 リリースの過程でしか確認できないファイル圧縮の失敗は予想できなかったのです。 結局、他の代案を探さなければなりませんでした。 RecoilライブラリをES5に変形する方法など考えてみましたが、おすすめしない方法だとネット上では勧告していました。 近年 BUYMA は IE をサポートしなくなりましたので、結合のファイルをES5に変更する過程をなくす方法もありました。しかし、その方法は影響範囲が多く、少人数で解決できないと考えられました。 そんな中、 BUYMA はReactライブラリを CDN ロードする方式で運用されていることが思い出しました。 近いところの一番簡単な方法が見つからず、遠いところの難しい方法だけを考えていました。 結局、Recoilの CDN を利用してuglify問題を解決する方法を採用することになりました。 Recoilのより良い開発経験のための整理 最近はまた別の画面でRecoilを使って開発をしました。 2回目の開発経験では、 命名 部分の理解度を高めるとRecoilを知らないエンジニアでもコードが理解しやすいかもという考えを持ちながらコードを作成しました。 selectorは以下のように関数の名前を 命名 しました。 意味的に派生したステートの名前を 命名 しようとしました。 コンポーネント では、上記の派生したステートの名前を簡単に理解できるようにコードを作成しました。 Recoilのフォルダとファイルの構造はステートの単位を意味するatomsと派生のステートを修正して新しい結果を作るselectorsに分離して使用することが便利でした。 /api /atoms /components /containers /hooks /selectors Recoilの開発の際にはReduxの開発の際と同様、意図しないステートの更新などを確認する必要があるので、開発ツールとして DebugObserver は必須だと思います。 ReduxのDevToolほど強力なツールではないと思いますが、シンプルな更新ログの形でいつも Chrome のDevToolsを開いて開発する私には見やすくてわかりやすかったので、不便ではありませんでした。 終わりに Recoilに対する小さな経験を話すことができて嬉しいです。 今度機会があればRecoilの多様なユーティリティ機能の使用経験を整理してみたいと思いました。 開発ツールの不在などReduxよりパワフルではないと思いますが、Reactフレンドリーな書き方や簡単な非同期処理など、開発しやすいRecoilライブラリの長所と魅力をこの記事で少しでも伝えられましたらと思います。 関連記事 Recoil: https://recoiljs.org/docs/introduction/installation グロー バルス テータス管理ライブラリ Recoil : https://abangpa1ace.tistory.com/212 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、エンジニアの岡本です。 BUYMA のWebアプリを作る仕事をしています。 この記事は Enigmo Advent Calendar 2022 の2日目の記事です。 弊社は10/22、23に開催された Kaigi on Rails にゴールドスポンサーとして参加し、更にオンラインブースを提供しました。 当日の雰囲気を知りたい方およびこれからテックカンファレンスのスポンサーをしたりブースを提供しようと思っている企業の方に参考にしていただけたら幸いです。 参加するまでの準備 上長の同意を得る 会社の 知名度 に寄与し、普段使っている OSS である Ruby および Ruby on Rails への貢献をする意味でも参加してみるのはどうかということで相談し同意を得ました。 予算の確保・稟議・スポンサー応募 スポンサー費用申請のため、人生で初めて稟議書を書きました。無事承認されて安堵しました。 そのあとは公式サイトからスポンサー応募を行いました。 ここまででも、スポンサーされている企業の担当者の方の苦労が手に取るように分かりました。地道なプロセスの上にテックイベントは成り立っているのです。感謝。 ブースの提供内容を考える 具体的に何をすれば良いか分からず、過去の参加記を調べた上で「社内の開発組織やエンジニアのパーソナリティを知ってもらう機会にしよう」ということにしました。 社内のエンジニアと人事担当者の協力を得て、ブースに参加していただき、それぞれテーマと時間割を決めました。 当日利用したタイムテーブル は Google docs で即興で作りました。 当日の雰囲気と反省 当日の発表は YouTube で公開されています。(以下はプレイリストになっています) www.youtube.com 一方のブース。なかなか参加している側としては見知らぬ人のところへ行くのは勇気がいることなので、序盤はブースに来ていただくのも大変でした。ブースに来てもらう工夫として Twitter /spatial.chatでの告知をしたり、ブースで話していることのテーマを画面に書いていました。 spatial.chatを使ってブース運営している様子 わかったこと 大きな Rails アプリケーションをどう運用しているかに関心がある人は一定数存在し、話題として需要がある 休憩時間にブースにくる人が増える。発表時間に来訪する人は少ない。発表の合間の休憩時間が20~30分くらいあることが何回かあるのでそこで 接触 を図る 競プロや Ruby ライブラリのHowToなどライブコーディング的なことをやっていたり、DB マイグレーション のスライドを用意して発表してる会社もあり、人が集まっていた ブースを出すならオフラインイベントで出せたらより楽しいのだろうなと感じました(筆者の主観に基づく) つぎやること オフラインでのスポンサー参加の検討(RubyKaigi、Kaigi on Rails など) エンジニアならではの企画を用意する(ライブコーディングの様子を公開する) ノベルティ の配布 発表の合間の休憩時間が20~30分くらいあることが何回かあるのでそこで 接触 を図る (必ずやるとは言っていない) 来年のKoR 2023年のKaigi on Rails はオフラインでも開催が検討されているそうです。2020年の開始以降、初めてのオフラインでの開催ということで盛り上がることを期待しています。 2日間ありがとうございました!クロージングで告知しましたが、2023はTokyoでハイブリッド開催を目指しています🎉 このアカウントをフォローして続報をお待ちください。 またお会いできるのを楽しみにしています〜!! #kaigionrails — Kaigi on Rails (@kaigionrails) October 22, 2022 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、インフラエンジニア の 加藤( @kuromitsu_ka )です。 先日、自社のメディアサービス( STYLE HAUS )のElastiCache for RedisのEOL対応(2.x→6.xへアップグレード)を実施しました。 環境の説明と今回やったことの概要 STYLE HAUSの環境は、 AWS に構築している本番環境とステージング環境、開発者のPC端末に構築しているテスト環境があります。 検証の段階で、PC端末に構築しているテスト環境(普段はlocalのRedisを使用している)から、ElastiCacheに接続して、バージョンアップの動作確認するため、今回、接続用の環境を構築しました。 ※ こちらの記事も参考になると思います。 aws.amazon.com ざっくりやること ElastiCacheと通信可能なサブネットにEC2 インスタンス を作成 EC2 インスタンス にてSSM Agentを起動( Amazon Linux2だとデフォルトでインストールされている) PC端末でSession Managerを実行 インフラの図 インフラ構成とIAM EC2 サブネット:ElastiCacheへ通信できるサブネットに作成 AMI: Amazon Linux2を利用 Amazon Linux2では、SSM Agentがデフォルトでインストール済み 参考: https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-setting-up.html インスタンス タイプ:t2.micro EC2の インスタンス プロファイル 参考: https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-getting-started-instance-profile.html 参考: https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/getting-started-restrict-access-quickstart.html 実際に インスタンス プロファイルに付与したポリシー { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": [ " ssm:GetDocument ", " ssm:DescribeDocument ", " ssm:StartSession ", " ssm:TerminateSession " ] , " Resource ": " * " } , { " Effect ": " Allow ", " Action ": [ " ssmmessages:CreateControlChannel ", " ssmmessages:CreateDataChannel ", " ssmmessages:OpenControlChannel ", " ssmmessages:OpenDataChannel " ] , " Resource ": " * " } , { " Effect ": " Allow ", " Action ": [ " ec2messages:AcknowledgeMessage ", " ec2messages:DeleteMessage ", " ec2messages:FailMessage ", " ec2messages:GetEndpoint ", " ec2messages:GetMessages ", " ec2messages:SendReply " ] , " Resource ": " * " } ] } 開発端末のIAMユーザー 参考: https://aws.amazon.com/jp/about-aws/whats-new/2022/05/aws-systems-manager-support-port-forwarding-remote-hosts-using-session-manager/ 実際にIAMユーザーに付与したポリシー { " PolicyVersion ": { " Document ": { " Version ": " 2012-10-17 ", " Statement ": [ { " Sid ": " 0 ", " Effect ": " Allow ", " Action ": " ssm:StartSession ", " Resource ": [ " arn:aws:ec2:ap-northeast-1:xxx:instance/<EC2インスタンスのID> ", " arn:aws:ssm:ap-northeast-1::document/AWS-StartPortForwardingSessionToRemoteHost " ] } ] } , " VersionId ": " v2 ", " IsDefaultVersion ": true , " CreateDate ": " 2022-09-28T06:55:24+00:00 " } } 作業者端末の手順 作業者のPC端末には、Session Manager プラグイン のインストールと、上記のIAMユーザーの設定が必要でした。 Session Manager プラグイン のインストール手順 参考( Mac 用の手順): https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html#install-plugin-macos 参考( Linux 用の手順): https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html#install-plugin-linux セッション確立のコマンド --targetは、今回、SSM agentを起動させているEC2のid --parameters '{"host": で指定するElastiCacheのエンドポイントを、必要な接続先に変換する。 localPortNumber は、端末や 仮想マシン の空いているポートを使う。 セッション開始!! $ aws ssm start - session \ -- target < EC2 インスタンスの ID >" \ -- document - name AWS - StartPortForwardingSessionToRemoteHost \ -- parameters '{"host":["<ElastiCacheのエンドポイント>"], "portNumber":["6379"], "localPortNumber":["16379"]}' \ -- profile < PC 端末に設定した IAM ユーザー> Starting session with SessionId : < IAM ユーザー>_connection_by_SSM-052ccf2de6652c027 Port 16379 opened for sessionId < IAM ユーザー>_connection_by_SSM-052ccf2de6652c027. Waiting for connections ... Connection accepted for session telnet 動作確認 aws ssm start-sessionのlocalPortNumberで指定した、 localhost のポートがElastiCacheと繋がっている。 $ (sleep 1 | echo quit)|telnet localhost 16379 Trying ::1... telnet: connect to address ::1: Connection refused Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. +OK Connection closed by foreign host. 感想 今回の接続環境の作成方法を知らなかった時は、Nginxで多段プ ロキシー を用意する案も考えていましたが、今回の構成は、短時間且つ簡単に構築できてよかったです。本来の目的だった、EOL対応も、問題なく、完了できてよかったです。😢 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
はじめに RubyKaigiが2019年以来の現地開催となり、2022年は 三重県 津市で行われました。 今年は現地と配信のハイブリッド開催であり、弊社から2名が現地参加、4名がオンラインで参加しました。 rubykaigi.org 過去の参加記 tech.enigmo.co.jp 本ブログには2017年の記録しか残ってないのですが、2019年まで毎年現地参加し、スポンサーをしている年もあります。 では、現地参加したメンバーとオンライン参加したメンバーそれぞれが感じたことを紹介します。 現地参加編 エンジニアの橋野です。新卒エンジニアとして4月からenigmoに入社しています。 詳しくは こちらの入社エントリ をご覧ください。 オフラインでおこなわれるカンファレンスは、初参加でしたのでとても楽しみにしていました。 初の現地開催参加という目線でレポートしていきたいと思います。 印象に残ったセッション Ruby Committers vs The World 数々の名言が残ったセッション。コミッターの方が自由に議論していてワクワクしました。 Ruby コミッターの方々が身近に感じられてとても楽しかったです。 Why is building the Ruby environment hard? 共感できることの多い発表でした。"ソフトウェアは何もしないと壊れる"しっかり胸に刻んでおきます。 The Better RuboCop World to enjoy Ruby RuboCopに苦しめられないための提案がとてもいい案だと思いました。楽しくRuboCopを使っていきたい...! スライドの挿絵がMidjourneyを使っていて、檻に閉じ込められた ruby が忘れられません。 感想 津駅に降り立ったのは人生初で、お昼ご飯や夕飯、スポンサー企業の方々からいただいた食べ物を通して三重に来たんだな〜〜!と主に食べ物で実感ができました。笑 また、会場には無料 Wi-Fi があり、コメントやメモがとても捗りました。駅からも少し距離があったのですが、 シャトル バスを出してくださったのでとても便利でした。スポンサー企業の皆様、ありがとうございます。 宿泊したホテルでは、同行者のstevenさんがエンジニアらしい数字?の404号室を引き当てていました! 最終日には、転職ドラフトのブースで404チャレンジというガチャガチャをしました。わたしはターミナルのピンバッチが当たったのですが、stevenさんはなんと404のピンバッチを見事当てていました。 会場には各地から集まった参加者がたくさんいて、こんなにも Ruby を好きな人たちがいるんだということを感じることができました。ここにいる人たちはほとんど Ruby を書いているということに感動しました。 また、休憩時間には普段なかなか交流ができない同世代のエンジニアとお話しができ、とても有意義に過ごすことができました。 普段あまり触れることのない技術の話も聞けて楽しかったです。 RubyKaigi運営のみなさん、スポンサー企業の方々、 Rubyist のみなさん、本当に素晴らしいカンファレンスに参加させていただきありがとうございました! オンライン参加編 新卒2年目の岡本です。主に BUYMA の出品者向け機能を開発しています。 Ruby の存在を知って5年ほど経ちますが、RubyKaigiに参加するのは今回が初めてです。 印象に残った発表 3日間、各セッションいずれも楽しませていただきました。個人的に印象に残っているのが以下です。 Ruby meets WebAssembly 1 Building a Lightweight IR and Backend for YJIT 2 ruby /debug - The best investment for your productivity 3 String Meets Encoding 4 Wasm対応について、 Webブラウザ で Ruby が動くことに感動しました。 irb がブラウザで動いている… irb-wasm.vercel.app デモの紹介は明快で、聴衆を引きつけた上で内部の実装の解説をされていて素晴らしいと思いました。 Ruby をWebAssemblyに変換する上で大きく3つの障壁 5 があり、それをAsyncifyという非同期処理を実現する アルゴリズム によって問題を解決しているとのことでした。 また Cookpad さんが提供されている Cookpad Code Puzzle for RubyKaigi 2022 にもWasmが取り入れられているようです。私はこれを書いている時点でfunc20まで解けていないのですが…解けると楽しいです。 ruby-puzzles-2022.cookpad.tech String Meets Encoding の発表について、業務で CP932 を扱ったりテキストを エンコード する場面によく出くわすので発表を楽しみにしていました。stackprofやperf、lldbを用いて Ruby でのstring encoding処理で ボトルネック になっている箇所を探索していく課程を垣間見れて大変勉強になりました。最終的にPRも作成されています。 6 YJITや ruby /debugは使ったことがなかったのですが、セッションを聞いて面白いと思ったのでアプリケーションに導入してみたいと思っています。 参加した感想 1. Ruby の開発の第一線に立つエンジニアの話を聞くのは刺激になる Ruby だけでなく普段使っている各種gemの作者・コミッターの方の発表をオンタイムで聴くとワクワクしました。またkateiさんのような才能あふれる方を見ると自分も頑張らないといけないと思いました。 2.オンラインだと落ち着いてメモがとりやすい 自宅からの参加ということで、外部キーボードとモニターに接続した状態で発表を視聴していました。2画面以上の環境で発表を聞きながら検索をしたり GitHub のissue/PRを閲覧できたので情報の摂取効率は高かったと感じています。(個人の感想) 3.ハイブリッド開催は大変 配信トラブルにより2日目の発表が一部聞けなかったのですが、スタッフの皆様の多大なご尽力のおかげで各セッションを楽しむことができました。この場をお借りしてお礼を申し上げます。 4.コンピュータ何もわからん Alan WuさんのYJITに関するセッションにおいて、バックエンド/フロントエンドという話題が上がりましたが、WebアプリケーションではなくCPU アーキテクチャ におけるそれでした。CPUの基本動作において、フェッチからデコードがフロントエンド、命令実行がバックエンドと言うらしいです。知らんかった… 最後に 2023年のRubyKaigiは長野県 松本市 で開催される予定です。2020年に現地開催されるはずだった松(Matz)本の地で Rubyist の皆さんとお会いできることを楽しみにしています!信州そばいっぱい食べましょう! お知らせ 弊社 エニグモ は2022年10月21日、22日に開催されるKaigi on Rails 2022にゴールドスポンサーとして参加します! 当日はオンラインブースを設置します。EM・テッ クリード などが参加予定です。 海外ファッションEC「 BUYMA 」の開発事情について・当日の発表についてなど、ざっくばらんにお話しできればと思います。 kaigionrails.org 採用情報はこちら hrmos.co https://speakerdeck.com/kateinoigakukun/ruby-meets-webassembly ↩ https://github.com/ruby/ruby/pull/5826 ↩ https://github.com/ruby/debug ↩ https://speakerdeck.com/ima1zumi/string-meets-encoding ↩ setjmp/longjmpに依存しているCRubyの例外機構・Fiberのcontext switch・保守的 GC ↩ https://github.com/ruby/ruby/pull/6351 ↩
アバター
こんにちは、テッ クリード の Steven です。 この記事で開発部門におけるメンタリングの体制を紹介して、学んだことを説明できればと思います。 メンタリングの目的 メンタリングはエンジニアが仕事を通して提供する価値が上がるようにサポートすることだと思います。 技術力を伸ばすのも重要ですが、仕事が全体的にもっと効率よく進むように仕事のやり方を改善するのがポイントです。 調査のやり方、コミュニケーションの取り方、時間管理、振り返り方、作業のタスク分けとスケジュールなど仕事を進める中で必要となるスキルを伸ばすことを目的としています。 メンタリングは ティー チングとも違っていて、どうすればいいのかをただ教えるのではなく、あくまでメン ティー をサポートして、自分一人で成長できるようにすることです。 何をすればいいのかを指示するのではなく、問題をどうすれば解決できるのか解決策を助言するか、判断しやすくなるようにアクションを提案することに留めます。 もちろん成長要素のない問題が発生した場合、正解が一つしかない場合、わざわざ考えさせる意味がないので、その時に限って答えを教えます。 手取り足取り、全てを細かく説明するのが優しいと感じられることがありますが、それだと、相手が自分で考えることなく、言われたことをそのまま実行するだけなので、成長には繋がりにくいと思います。 メンターがいなくなって、新しい問題が発生したら、解決するには多分苦労するでしょう。 メンタリングはその状況を避けるためにあって、優しさより成長を優先すべきだと思います。 当然ですが、メンタリングは社員全員にではなく、新卒やジュニア限定としています。 それも当人の技術力、仕事の捌き方によって決まるもので、基準値を超えれば、メンタリングを終わりとします。 メンタリングの体制 開発部門でメンタリングを受けるメン ティー にメンターが一人付きます。 そのメンターは日々の作業の手助け、成長のためのサポート、作業のレビュー、週次振り返りを担当します。 メン ティー のために時間が取れるように、メンターは基本的に一人のメン ティー だけを担当します。 メン ティー は 1on1 という対面月次振り返りも受けます。 対面相手はリーダー層のエンジニアになります。 それとは別でメンターのメンタリングのための会議も毎月実施しています。 メンターとメンタリングの経験があるエンジニアが集まる MTG です。 メン ティー の週次振り返り 毎週メン ティー とメンターで振り返り MTG を実施して、一週間の間にメン ティー が行った作業と、発生した問題を分析して、改善案を出します。 YWT の形で行っていて、やったこと(Y)、わかったこと(W)、次やること(T)を事前にメン ティー に記事にまとめていただいてから、メンターと二人で話し合います。 YWT の記事でまとめるのは作業内容も含まれますが、それよりも仕事の進め方を改善するために取ったアクション(Y)、作業をする中でやり方についてわかった重要なこと(W)、次の振り返りまで仕事の進め方を改善するために取る予定のアクション(T)というのが内容となります。 技術についてわかったことは自然と増えて、メンタリングをそんなに必要とするものでもないので、それで時間を浪費しすぎないように気をつけています。 その記事を確認した上でメンターはメン ティー とディスカッションをして、時間の使い方がよくないとか、調査方法が非効率だとか、メン ティー が抱えている問題を掘り出して、改善アクションを提案します。 メンターはできるだけ、表面的な問題ではなく、根底にある問題を暴き出すように努めます。 改善アクションが決まれば、口頭で終わらせるのではなく、アクションをしっかりと振り返り記事に記録して、次の振り返りで実施されたかどうか、フォローアップを行います。 アクションがわかりやすくまとめられて、実施される前提でメンターが振る舞えば、メン ティー もやる気が出て、アクションが取りやすくなるかと思います。 YWT の例 以下は振り返りに慣れたエンジニアが実際に書いた YWT です。 入社したてのメン ティー でこんな風に状況を分析して情報をまとめるのが難しいと思いますが、目指すべき振り返りの形だと思います。 レビュー入力欄改善と検索UIはプロジェクトの名前です。 Y(Yattakoto) レビュー入力欄改善はキリのいいところで一旦打ち切った 検索UIでは figma や画面仕様を見ながら詳細設計した W(Wakattakoto) プロジェクトに アサイ ンされたばかりだが画面仕様書の作成を通して実装できるぐらいには仕様を把握することができた 検索UIのチケット1枚あたりの作業量は膨大ではないのですぐ終わってモチベーションが保ちやすい。レビュー入力欄改善のタスクはチケット1つで結構な作業量なのでチケットを細分化する必要があると感じた。 コンポーネント 作成のタスクに入ったが、 css の適用はしないのででき上がるviewは不完全なものになるのでタスクはどうやったら完了なのかが曖昧なのがわかった T(Tsugiyarukoto) ペアプロ を通してタスクの完了ラインについて把握する メン ティー の 1on1 毎月メン ティー とリーダー層のエンジニアの間で 1on1 という対面振り返り MTG も実施しています。 1ヶ月において、行った作業と成長したところ、まだ抱えている問題を確認します。 目的は週次振り返りと同じで、仕事のやり方に対する問題の掘り出しと、改善アクションの提案です。 メンターと違う方が見ることで、メンターが見逃した問題を発見することもできれば、メンターに相談しにくい話も聞けます。 それに加えて、人事考課で設定した半期の目標の進捗も確認して、進捗が悪ければ、アクションプランを提案します。 カウンセリングという ストレスチェック も行って、作業量が多すぎるとか、最近成果が出せてないとか、ストレス要素の排除に努めます。 メンターのメンタリング メンタリングは制度だけではなく、メンターのスキルでもあるので、そのスキルが磨かれるようにメンターのメンタリングも行っています。 初めてメンターになる方の場合、最初のうちはメン ティー の週次振り返りに経験者も同伴します。 最初の会はその経験者が仕切ってメンタリングのやり方を見せますが、それ以降はメンターに任せて、サポートに徹します。 それとは別で、毎月現役メンターとメンタリングの経験者が MTG で集まります。 メンターによるメン ティー の話を通して、次にメンターが取るべきアクションをディスカッションします。 メンターのやり方に問題があるとわかれば、何に気をつけるべきか、どんな風に問題を解決すべきかと伝えて、アクションを取っていただきます。 軌道修正の意味合いが強くて、あくまで問題があれば指摘をして、それ以外はメンターに判断を任せます。 メンターも月次 1on1 を受けることがあるので、必要に応じてその場面でもメンタリングに対する助言をします。 学んだこと 当然かもしれませんが、メンタリングにおいても明確な目標を持った方がいいです。 メン ティー の課題を分析した上で目標を設定して、達成のためにアクションを取っていただいて、改善の経過を計測するのが重要です。 日々発生する細かい問題をスポットで解決するだけではメンタリングの本当の効果は得られず、メン ティー の成長に時間がかかってしまいます。 日々の作業の中で発生する表面的な問題を通して、メン ティー がかかえている根本的な課題を掘り出すのもポイントです。 特定の機能の ソースコード の場所がわからなくて、ずっと悩んでいて作業が進まなかったというのが問題であれば、課題は ソースコード の場所を覚えてないのではなく、調査方法に問題があるか、すべきだった相談をしなかったというところにあります。 そこからメン ティー とのディスカッションを通してより細かい原因を割り出して(迷惑をかけたくなかったので、相談をしなかったとか)、改善アクションを決めるのが適切だと思います。 ここで ソースコード の場所を教えるだけだと、問題は一時的に解決しますが、おそらく違う形でまた発生するので、根本的な解決にはならないです。 どの時点でメンタリングが不要なのかと基準を設けるのもいいでしょう。 試験とまで行かなくても、誰でも確認できる、共 通化 された基準があると、メンターとしてもメン ティー としてもやりやすくて、やる気も出ると思います。 最後に、メンタリングの目的とやり方はメンターの間でブレることがあるので、認識合わせをしっかりと行った方が安全です。 特にメンタリングの経験が少ない方だと、メンタリングと ティー チングの違いを把握しきれないことがあるので、時間を取ってゆっくりと説明した方がいいと思います。 メンタリングで行うことを明文化して、流用できるガイドを用意できると、ベストかもしれません。 終わりに エニグモ はもともと 中途採用 のみでしたので、メンタリング制度はありませんでしたが、新卒を積極的に採用することとなって、メンタリングの必要が現れて、どうやっていくか色々考える必要がありました。 メンタリングを通して、メンターがメン ティー の代わりに仕事をしてしまうとか、表面的な問題しか解決できずメン ティー のレベルが上がらないままでいるとか、望んでいたのとは違う結果になってしまうこともあるので、やり方に気をつけるべきです。 メンタリング制度を導入することで新卒の育成だけではなく、開発部門で振り返り文化が根付いて、メンタリングを受けてないメンバーの仕事のやり方もより早く改善するようになったと思います。 エニグモ に入った時にまだなかった文化ですが、未開拓地だった場所に色々道路と橋が建てられて、成長の場としてはよりいいところになったと思います。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、インフラエンジニア の 加藤( @kuromitsu_ka )です。 今回は、Sambaのアクセス制御を DNS 名で実施したので必要だった設定を記載します。 エニグモ 社では、令和の今もオンプレミスでSambaを利用した古き良きログの集約サーバが稼働しています。この度、オンプレミスのログサーバと同じ構成のものを AWS に構築することになりました。その際、Sambaのアクセス制御で少し工夫した部分があったので、ログを残します。 エニグモ 社のログ集約サーバの仕組み ログサーバからアプリサーバごとに ディレクト リを作成して、Sambaインストール済みのサーバをログサーバからマウントすることでログを集約しています。インフラチームでない開発者は、基本的にサーバへ SSH できないので、ログサーバだけアクセスを許可することで、ログを確認できるようにしています。 Sambaのアクセス制御を DNS 名で設定する動機 オンプレミスのSambaのアクセス制御は、公開 ディレクト リごとにIPセグメントやログサーバのIPを指定してアクセス許可設定しています。オンプレミスのサーバでは、プライベートIPが固定でしたが、 AWS では、サーバが再作成されても大丈夫なように、 DNS 名でアクセス制御することにしました。 オンプレミスでのSambaアクセス許可設定 hosts allow = ログサーバのIPや、本番環境のIPセグメント AWS では、SGでアクセス制御をしてもよかったのですが、運用上が面倒なのでSambaの設定でアクセス制御することとなりました。 AWS での理想のSambaアクセス許可設定 hosts allow = ログサーバのホスト名 Sambaサーバの DNS 名でのアクセス制御に必要だった設定 ログサーバからマウントされるアプリやDBのサーバ(Sambaサーバ側)ではレコード引きを許可する設定と、 ホワイトリスト 設定が必要で、ログサーバ(Sambaクライアント側)では、ログサーバの逆引きレコードが必要でした。 必要だった設定(Sambaサーバ側) ホスト名のIPをレコード引きを許可する設定が必要でした。 ホスト名検索の許可 dns proxy = yes hostname lookups = yes ホワイトリスト 設定 hosts allow = ログサーバのドメイン名 必要な設定(Sambaクライアント側) Sambaのアクセスには、PTRレコードが必要でした。 CloudFormationでログサーバ作成と同時に逆引きレコードも設定できるようにしました。 RecordSetPtr : Type : AWS::Route53::RecordSet Properties : HostedZoneId : ${PTRレコードホストゾーン} Type : PTR Name : !Sub - ${FourthOctet}.${ThirdOctet}.${SecondOctet}.${FirstOctet}.in-addr.arpa - FirstOctet : !Select [ 0 , !Split [ "." , !GetAtt Instance.PrivateIp ]] SecondOctet : !Select [ 1 , !Split [ "." , !GetAtt Instance.PrivateIp ]] ThirdOctet : !Select [ 2 , !Split [ "." , !GetAtt Instance.PrivateIp ]] FourthOctet : !Select [ 3 , !Split [ "." , !GetAtt Instance.PrivateIp ]] ResourceRecords : - !Sub - ${良きホスト名} TTL : 300 デバッグ ログ Sambaサーバの DNS 名でのアクセス制御に、逆引きレコードが必要となった際のログです。 PTRレコードがない状態でのアクセス Sambaクライアント側からだと弾かれてしまいます。 [root@SambaClient ~]# mount.cifs //${Sambaサーバ}/test test -o vers=3.0,password=,dir_mode=0755 -vvv mount.cifs kernel mount options: ip=10.195.101.57,unc=\\10.195.101.57\syslog,vers=3.0,dir_mode=0755,user=root,pass=******** mount error(13): Permission denied Refer to the mount.cifs(8) manual page (e.g. man mount.cifs) Sambaサーバ側のログ IPベースで制御していそうでした。「matchname failed on 10.195.100.9」 ※この際、SambaクライアントのIPは 10.195.100.9 Denied connection from 10.195.100.9 (10.195.100.9) [2022/04/11 16:44:43.212303, 0] ../../source3/smbd/server.c:1788(main) smbd version 4.10.16 started. Copyright Andrew Tridgell and the Samba Team 1992-2019 [2022/04/11 16:44:43.266814, 0] ../../lib/util/become_daemon.c:136(daemon_ready) daemon_ready: daemon 'smbd' finished starting up and ready to serve connections [2022/04/11 16:44:45.916505, 0] ../../lib/util/access.c:365(allow_access) Denied connection from ip-10-195-100-9.ap-northeast-1.compute.internal (10.195.100.9) [2022/04/11 16:45:17.430789, 0] ../../lib/util/access.c:365(allow_access) Denied connection from ip-10-195-100-9.ap-northeast-1.compute.internal (10.195.100.9) 逆引きレコードのホスト名で、弾かれていることが判明しました。 # nslookup 10.195.100.9 9.100.195.10.in-addr.arpa name = ip-10-195-100-9.ap-northeast-1.compute.internal. Authoritative answers can be found from: 動作確認 DNS 名で許可しているサーバからのみ、Sambaマウントできることを確認します。 許可しているログサーバの DNS 名は、 logserver です。 Sambaサーバの許可設定 [root@SambaServer ~]# cat /etc/samba/smb.conf [global] : dns proxy = yes hostname lookups = yes : include = /etc/samba/smb.d/test.conf [root@SambaServer ~]# [root@SambaServer ~]# [root@SambaServer ~]# cat /etc/samba/smb.d/test.conf [test] : hosts allow = 127. logserver 逆引きを設定していおらず、アクセス許可もされていない他のクライアントからだと、Sambaマウントできない。 [root@NotAllowedSambaClient ~]# hostname -I 10.195.100.9 [root@NotAllowedSambaClient ~]# [root@NotAllowedSambaClient ~]# [root@NotAllowedSambaClient ~]# nslookup 10.195.100.9 9.100.195.10.in-addr.arpa name = ip-10-195-100-9.ap-northeast-1.compute.internal. Authoritative answers can be found from: [root@NotAllowedSambaClient ~]# [root@NotAllowedSambaClient ~]# [root@NotAllowedSambaClient ~]# mount.cifs //${Sambaサーバ}/test test -o vers=3.0,password=,dir_mode=0755 -vvv mount.cifs kernel mount options: ip=${Sambaサーバ}/,unc=\\${Sambaサーバ}/\test,vers=3.0,dir_mode=0755,user=root,pass=******** mount error(13): Permission denied Refer to the mount.cifs(8) manual page (e.g. man mount.cifs) 逆引きレコード設定済みのアクセス許可のあるログサーバ( logserver )からだと、Sambaマウントできる。 [root@logserver ~]# hostname -I 10.195.116.243 [root@logserver ~]# [root@logserver ~]# [root@logserver ~]# nslookup 10.195.116.243 243.116.195.10.in-addr.arpa name = logserver.honyarara.com. Authoritative answers can be found from: [root@logserver ~]# [root@logserver ~]# [root@logserver ~]# mount.cifs //${Sambaサーバ}/test test -o vers=3.0,password=,dir_mode=0755 -vvv mount.cifs kernel mount options: ip=${Sambaサーバ},unc=\\${Sambaサーバ}\test,vers=3.0,dir_mode=0755,user=root,pass=******** [root@logserver ~]# [root@logserver ~]# [root@logserver ~]# df -h ファイルシス サイズ 使用 残り 使用% マウント位置 : //10.195.101.57/test 50G 19G 32G 38% /root/test 感想 今回は、古のシステムを触ったので供養ブログとなりましたが。 事例がパッと見つからない課題は、毎回取り組むのが楽しいです。 困った時は、ドキュメントを読むのが大事ですね。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは! 株式会社 エニグモ に22年新卒入社しました橋野です。 サービスエンジニアリング本部で BUYMA のサービス開発を担当しています。 今回の記事では、 エニグモ への入社理由についてお話ししようと思います。 目次 わたしとは? コロナ禍のエンジニア就活 内定承諾を決めた3つの理由 エニグモのサービス 世界を変える新しい流れを エニグモで働く人 おわりに わたしとは? まずは簡単に私について自己紹介できればと思います。 学生時代は ハッカソン が好きで、たくさん ハッカソン に出場したり、また私自身もプログラミングの団体に所属しており ハッカソン を主催したりしたこともありました。 ハッカソン にたくさん出場するようになったきっかけは、人数合わせで初めて出た ハッカソン で悔しい思いをしたので、なんとかリベンジをしたいと思ったからです。たくさん出るうちに楽しくなっていき、どんどんのめりこみました。 テックイベントに参加するのも好きで、コロナ禍の前はよく学校終わりに勉強会やLT会などにも積極的に参加していました。 エニグモ でも勉強会がほぼ毎週開催されているので、参加しています。 実は、元々小さい頃から機械が好きで、小学生の頃からロボットをいつか作ってみたいなと思っていました。大学3年生のときに、 マイコン を購入し、おもちゃを趣味でつくったこともあります。そういうところも、 ハッカソン やプログラミングに興味を持つきっかけになったかもしれないです。 コロナ禍のエンジニア就活 大学3年生にあがったくらいで就活を意識し始めました。 新卒エージェントサービスを使ったり、 Wantedly や逆求人イベント等のエンジニア向けのサービスを使ったりしてたくさんの企業を見ました。 勤務拠点が東京の会社を希望していたので、オンラインメインの就活は関西に住んでいた私にとって長距離の移動が少なく活動しやすい環境だったと思います。 ただ、自宅から面接を受けていたので、面接中は家族に協力してもらったりと気を遣わすこともあり、家族は大変だと思います。 また、友人にESを添削してもらったり、就活の相談に乗ってもらったりしました。 家族や友人の協力があってこその就職活動だったと思います。 エンジニア就活で色々と焦りや不安を抱えることはあったのですが、妥協せずに最後の最後まで就活をしました! 内定承諾を決めた3つの理由 選考を受けるまで、 BUYMA で1回買い物をした程度で エニグモ が BUYMA を運営していることは知りませんでした。 エニグモ との出会いは就活をしている時に、たまたま エニグモ で働いているエンジニアをフォローしており、 中途採用 募集のツイートを見かけました。 また、今までのツイートから エニグモ のリアルを事前に知ることができ安心感がありました! 選考のスピードは他社と比べて圧倒的に早く、そこもプラスの点でした。 私が最終的に、 エニグモ へ内定承諾を決めた理由は下記の3つとなります。 エニグモ のサービス ECサービスが好きなので、サービス開発からユーザーの売り買いに携われたらいいなとぼんやり考えていました。 ネットショッピングは見ているだけで楽しく、ワクワクするところやサービス自体がユーザーに商品を買わせようと工夫しているところが面白いです。 わたしはより快適に買い物ができたり、新しい出会いがうまれる機会を提供できるサービスの開発をできるようになりたいと就活のサービス選びの軸として1つ持っておりました そんな時に、見かけた BUYMA の開発ができる求人に迷わずにここだ!と思い応募しました。 Twitter で見かけた求人は 中途採用 のものだったのですが、中途しかなくてもとりあえず応募しようという覚悟でした。 そんな思いとは裏腹に エニグモ は新卒も通年採用をやっていたので、どの時期に選考を受けても歓迎してくれました。 世界を変える新しい流れを わたしは、大学生3年生の頃に「新しい価値」を提供できるようになりたいと思いました。学生の頃は学生なりに取り組んでいましたが、社会人になっても会社を通してもっと大きなことに挑戦したいと考えていました。 エニグモ には、「世界を変える新しい流れを」というミッションをかかげており、 そのミッションに共感し、 エニグモ で新しい価値を提供していきたいと思っています。 「世界を買える」と「世界を変える」をかけているらしく、おもしろいですよね。 エニグモ で働く人 エニグモ で働いている人は、やさしい人が多いです! 面接や面談でも私自身に興味を持ってくださり、知ってくれようとしたことが印象的でした。また、自分を着飾るのではなく、正直に話したときに受け止めてくださったことが嬉しかったです。 入社後も部署関係なく交流ができたり、登山が好きな人がいたり、楽しい人が多いです。 (登山は、誘っていただきましたが運動不足ということもあり少しハードルが高くてまだ参加できていませんが...) ファッションECというだけあって、スタイルハウス編集部や BUYMA のMDの方々はとてもおしゃれな方が多いなと思いました。(エンジニアはおしゃれやファションに興味がそこまである人ばかりではないので安心してください。) おわりに 現在は OJT を受けながら BUYMA の開発をしています。自分自身早く現場に入って開発をしたいと思っていたので、楽しく、刺激のある毎日を送っています。 まだまだメンターのサポートありきで、開発しているので早くチームの力になれるようにたくさん吸収していこうと思います。 登山好きな人は多いのですが、 ボルダリング が好きな人は少ないのでぜひ ボルダリング に興味がある方もお待ちしています!笑 エニグモ に興味ある方は、下記募集チェックお願いします。 新卒採用も通年採用で募集しております。 株式会社 エニグモ すべての求人一覧 https://hrmos.co/pages/enigmo/jobs
アバター
はじめまして! 2022年4月に エニグモ へ新卒入社した川本です。早いもので入社して2か月がたちました。 この記事では、コロナ渦での就活についてや、なぜエンジニアを目指したのか、そしてなぜ エニグモ に入社を決めたのかについて書いていこうと思います。 新卒でエンジニアを目指している方、 エニグモ に興味を持っている方へ、少しでも参考になれば幸いです。 目次 自己紹介/エンジニアを目指したきっかけ 就職活動について コロナ禍での就活で大変だったことや気を付けたこと エニグモの採用(説明会・面接・面談)への印象 エニグモに入社した理由 入社して2ヶ月たち感じること 今後の抱負 最後に 自己紹介/エンジニアを目指したきっかけ 2021年12月から インターン として エニグモ で働いており、2022年4月に新卒入社しました。 サービスエンジニアリング本部に所属し、海外通販サイト『 BUYMA 』のWebアプリケーション開発に携わっています。 大学時代はア マチュア キックボクサーとして活動していたのですが、骨折して動けない期間に暇になり、家の中でもできる何か新しいことを始めようと思い、はじめて自分でパソコンを買いました。 それまでは、実はマイパソコンすら持っておらず、大学のパソコンルームでレポートなどを書いていました... 最初はこれから研究室の配属もあるし、パソコンスキルを身につけようかといった考えで購入しました。 大学の 数値計算 の講義で、 Fortran という プログラミング言語 に触れたことや、大学院に新設されたAIのコースを知って、プログラミングやITに少し興味を持ったことをきっかけに、ドットインストールなどのオンライン学習サービスでプログラミングの勉強をするようになりました。 とはいっても、私は大学で 海洋学 を専攻していてITとは無縁な所にいたので、この時はまだエンジニアになろうとは全く考えていませんでした。 その後も個人で学習は継続して、大学3年の夏に初めてWEB系企業の インターン で開催していた ハッカソン に参加しました。この ハッカソン はランダムに組まれた3人チームで行い、自社の API を使ってWEBアプリケーションを作るといった内容でした。 初めての ハッカソン の参加でしたが、チームメンバーにも恵まれて最優秀賞をいただくことができました。 この時初めて自分達で考えたア イデア をサービスにして、そのサービスを使ってもらいフィードバックをもらうという経験をしたのですが、この体験にとてもやりがいを感じることができ、エンジニアとして仕事がしたいと思うようになりました。 就職活動について コロナ禍での就活で大変だったことや気を付けたこと この記事を読んでいただいている、就活生の皆さんにとっては既にリモート就活が一般的かもしれませんが、私の感じたリモート就活のメリット、デメリット、気をつけたほうがいいことを紹介できればと思います。 メリット 企業に足を運ぶ必要がないため、一日に複数社の選考を受けることができます。 交通費の節約になる。特に地方の学生は、IT系などの東京に集中している企業は受けやすくなったと思います。 大学と就活の両立がしやすい。大学にいながら研究の合間に面接を受けることができるのはとても助かりました。 デメリット 会社や面接官の雰囲気がわかりにくい。 自分の意見が相手に伝わっているかどうかわかりにくい。 気をつけたこと デメリットに挙げた通り、リモートだと会社や面接官の雰囲気がわかりにくいことがあると思います。 最初のカジュアル面談や一次面接はリモートでいろんな企業を受けることに非常にメリットを感じましたが、選考が進めばどこかのタイミングで対面での面接の機会を設けることをおすすめします。 実際にオフィスを見学したり、働いている社員の方々を見ることで感じることも多いと思います。 私は内定承諾する際は、内定先の企業に実際に訪れるようにしていました。その中で エニグモ の雰囲気が一番自分にあっていると感じて内定承諾しました。 エニグモ の採用(説明会・面接・面談)への印象 就活を始めた初期に エニグモ の求人をたまたま見つけて、そこで初めて BUYMA を運営している企業なのだと知りました。 BUYMA のことは以前から知っていて、使ったこともあったので、まずはお話を聞いてみようといった感覚で応募しました。 エントリー後は、人事面談、一次面接、二次面接、最終面接といった選考フローで進んでいきました。 エニグモ の採用で驚いたことは、一次面接からエンジニア部門の部長やマネージャーの方に面接していただけることです。 他社では一次面接は人事、二次面接では現場のエンジニアといったケースが多かったため、珍しいなと感じました。 内定後も新卒で エニグモ に入社したエンジニアの方との座談会の機会を設けていただきました。同年代の方にざっくばらんに質問することができて、自分の中で最終確認することができました。 エニグモ に入社した理由 私が エニグモ へ入社を決めた理由は、大きく三つあります。 一つ目は、CtoCのサービスを扱っていることです。私は大学で 海洋学 を専攻していたため一次産業が身近で、生産者と消費者を繋ぐプラットフォームについて興味がありました。 BUYMA はファッションをメインに扱っていて一次産業とは異なりますが、ユーザーとユーザーを繋ぐCtoCという面でシステム的に近いプラットフォームであったため、 BUYMA の開発をしてみたいと思いました。 二つ目は、自分の興味や、やりたいことを尊重してくれるところです。私は学生時代はデータ基盤の開発しかほぼやったことがなく、アプリケーションの開発はあまりやったことがありませんでしたが、アプリケーションの開発がやりたいと伝えたところ柔軟に受け入れてくださいました。 三つ目は、 エニグモ の社員の人たちの雰囲気がいいなと思ったからです。選考を通して、決まった質問をする感じではなく丁寧に私のことを考えて質問してくださっている印象があり、一緒に働きたいと思える方が多かったです。 入社して2ヶ月たち感じること 入社前から抱いていたイメージと大きく違うことはありませんでした。 ただ、想像していた以上に BUYMA は大きなシステムだなと思いました。 当たり前かもしれませんが、これまで私が ハッカソン や個人で開発していたシステムとは ソースコード の量が全然違い、理解するのが大変です。 しかし、大きなシステムである分知らないことがたくさんあるため、毎日新しい学びがあり成長を実感できるため楽しく開発ができています。 今後の抱負 まずは目の前の業務をしっかりと一人でこなせるようになりたいです。 今はまだ経験が浅いので、いろんな分野を経験して、自分の興味がどこにあるのかを見つけて、その分野に強みを持てるようになっていきたいです。 また言われた通りに開発するだけでなく、しっかりと自分の意見を持ちサービスをより良くするためにどうするべきか考えて実装できるエンジニアになりたいと思っています。 最後に 今はエンジニアが人気職業になって世の中に様々な情報があふれているため、エンジニアを目指す就活生にとっては、何が正しいのか中々判断がつきにくいと思います。 私も非情報系学部の学生だったためその一人でした。 そんな私が就活を通して大切だと感じたのは、 ハッカソン や インターン に参加して同じ学生のエンジニア仲間を作ったり、現役のエンジニアと交流することです。 周りの同じエンジニア志望の学生が何をしているのか、現役のエンジニアはどのようにしてエンジニアになったのかを知ることで、自ずと自分に今何が足りなくて、これから何をすればいいのかわかってくると思います。 またそういった仲間がいると技術の話や就活の話を共有できるため、楽しく学習を継続することができてモチベーションも保てると思います。 最後までお読みいただきありがとうございます。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、インフラエンジニア の 加藤( @kuromitsu_ka )です。 今回は、 Amazon Aurora のポイントインタ イムリ カバリ(特定時点へのリストア)を触ったので、記事を残します。 概要 Auroraのバックアップ保持期間内であれば、特定の時点のデータで、DB クラスタ を作成できる機能があり、これが便利でした。 DB クラスタ の作成にかかる時間と、DB作成時にどこまで正確にデータを復元できるか確認したので、そのまとめを記載します。 ※手順の方は、ドキュメントに記載あるので割愛します。 ざっくりよかったこと バックアップ保持期間であれば、秒単位で指定した時点でDB クラスタ を作成できる。 運用中のDBを切り戻すのでなくDBを新規作成するため、現行DBに手を加えなくて良い。 ちょっと細かいところ。 Auroraは、 フルバック アップと合わせて、 トランザクション データも保存しています。 一番早くて、最新時間の5分前の時間で、DB クラスタ を作成できる模様でした。 ※切り戻せる時間の範囲については、 describe-db-clusters  から確認できます。 参考になる公式ドキュメント 特定の時点への DB clusterの復元 Aurora DB クラスターのバックアップと復元の概要 検証したこと ざっくり2つ確認しました。 DB クラスタ 再作成にかかる時間の計測 切り戻し時点の前後の トランザクション データの正確性の確認 (検証1)DB クラスタ 再作成にかかる時間の計測 Aurora クラスタ のステータスが、作成中から使用可能になるまでの時間を確認しました。 モニタリング用に、Aurora クラスタ のステータス確認 スクリプト も作成しました。 $ cat check.sh #!/bin/bash while true do echo $( date + " %Y%m%d %H:%M:%S "; aws rds describe-db-clusters --db-cluster-identifier $1 | jq -r " .DBClusters[].Status " ) sleep 1 done 結果としては、以下のようになりました。 積んでいるデータが大きいと、そこそこ時間はかかりそうなものの、まぁ大丈夫かと。 データ 所用時間 インスタンス タイプ データのサイズ 開発環境データ 15分程度 db.t3.small(vCPU:2,メモリ:2GiB) 30GB 本番環境データ 27分程度 db.r6g.4xlarge(vCPU:16,メモリ:128) 160GB おまけの計測 Auroraのリストアも、 フルバック からリストア後、差分適用してるのかな?と思い。 開発環境にて、 フルバック アップ取得時間を軸に、ざっくり2パターンで検証しました。 結果として、特に大差はなかったです。 Auroraの フルバック アップ取得から、1時間経過した時点のデータでDB作成した場合 →約13分30秒 Auroraの フルバック アップ取得より、1時間前(23時間経過した)の時点のデータでDB作成した場合 →約14分00秒 (検証2)切り戻し時点の前後の トランザクション データの正確性の確認 切り戻したい時間帯に発生していた トランザクション のデータは、ちゃんと復元できるのか確認しました。 検証の結果 リストアでデータが正確に復元できないのは、リストアで指定する時間の前後1秒間にコミットされていない トランザクション だけでした。 具体的な検証方法 スクリプト でINSERT,UPDATEを流して、 トランザクション を発生させました。 スクリプト 実行中の時間を指定して、DB クラスタ を作成して起動後のデータを確認しました。 検証ログ データの追加時間・更新時間がわかるように、こんな感じのテーブルを作成して トランザクション を発生させました。 +------------+-------------+------+-----+-------------------+-----------------------------+ | Field | Type | Null | Key | Default | Extra | +------------+-------------+------+-----+-------------------+-----------------------------+ | id | int(11) | NO | PRI | NULL | | | comment | varchar(20) | YES | | NULL | | | created_at | timestamp | NO | | CURRENT_TIMESTAMP | | | updated_at | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP | +------------+-------------+------+-----+-------------------+-----------------------------+ スクリプト のINSERT,UPDATE部分 外部のサーバから、任意の数の トランザクション を発生させる スクリプト です。 ※DELETEも流していた時の名残もありますが、こちらはスルーでお願いします。 for i in $(seq ${NUMBER_OF_QUERIES_INSERT_and_UPDATE}) mysql -h $RDS -u${user} -p${password} -e "INSERT INTO ${TABLE_NAME} (id, comment) VALUES (${i},\"insert${i}\");" mysql -h $RDS -u${user} -p${password} -e "UPDATE ${TABLE_NAME} set comment=\"update${i}\" where id=${i};" # if [ ${i} -lt ${NUMBER_OF_QUERIES_DELETE} ] || [ ${i} -eq ${NUMBER_OF_QUERIES_DELETE} ]; then # mysql -h $RDS -u${user} -p${password} -e "DELETE FROM ${TABLE_NAME} WHERE id = ${i};" # fi done echo $(date "+%H:%M:%S") Script END >> ${STATUS_FILE} スクリプト の出力するステータスファイルから開始終了時間を確認 # cat /tmp/status.txt 2022-01-31 11:58:21 Script START 2022-01-31 21:52:14 Script END DB クラスタ ー作成 スクリプト 終了間際の、2021/01/31 21:52:00に切り戻します。 リストア後のテーブル 2021/01/31 21:51:59秒までのデータは、問題なく切り戻せていました。 mysql> select * from bm_messages.AWSNEXT_1215 ORDER BY id desc limit 5; +--------+--------------+---------------------+---------------------+ | id | comment | created_at | updated_at | +--------+--------------+---------------------+---------------------+ | 379369 | insert379369 | 2022-01-31 21:51:59 | 2022-01-31 21:51:59 | | 379368 | update379368 | 2022-01-31 21:51:59 | 2022-01-31 21:51:59 | | 379367 | update379367 | 2022-01-31 21:51:59 | 2022-01-31 21:51:59 | | 379366 | update379366 | 2022-01-31 21:51:59 | 2022-01-31 21:51:59 | | 379365 | update379365 | 2022-01-31 21:51:59 | 2022-01-31 21:51:59 | +--------+--------------+---------------------+---------------------+ 5 rows in set (0.01 sec) 感想 DBを切り戻すんじゃなくて、別途作成してくれるところがいいなぁって思いました! 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、 エニグモ でデータサイエンティストをしている堀部です。 昨年末から使い始めたdbt x BigQueryについて共有します。 BigQuery歴2年、 SQL 歴5年ほどになります。 QUALIFY句が好きです。 dbtを使い始めたきっかけ 使ってみてよかった点 前処理〜特徴量生成の例 dbt_project.yml macro models Appendix:packageの利用 まとめ dbtを使い始めたきっかけ SQL での集計は嫌いではないのですが、以下の2点で困っていることがありました。 1点目は、BigQuery特有のエラー Resources exceeded during query execution: Not enough resources for query planning - too many subqueries or query is too complex (以後、too complex エラー)です。 create temp table を使って一時的な中間テーブルを挟むことで回避してきたのですが、その都度書き換えるコストがかかっていました。 2点目は、似たようなクエリを以前に書いたこと覚えがあっても、過去の自分が書いたクエリ *1 が長く該当箇所を見つけるのに時間がかかってしまうという課題がありました。 この2点をまとめて解決できそうだと感じ、使い始めたのがdbtでした。 使ってみてよかった点 with句を分割して管理できる → 部分的なクエリの再利用がしやすくなった データモデルの種類(view、table、intermediate、ephemeral)を簡単に変更できる → too complexエラーの回避が簡単に yaml (dbt_project.yml)で複数のクエリで共通に利用できる変数(vars)を管理することができ、 CLI で変数を上書きして実行することができる → 汎用的なクエリを作成して、varsだけを変更することで様々なパターンを試せるようになった また、jinja2を使ったmacroを前処理〜特徴量生成で利用してみたら便利だったので紹介します。 前処理〜特徴量生成の例 bigqueryの公開データの bigquery-public-data.ml_datasets.census_adult_income を使って実際に利用したファイルを元に紹介します。 *2 dbt_project.yml modelsとvarsの部分のみを変更しています。 # Name your project! Project names should contain only lowercase characters # and underscores. A good package name should reflect your organization's # name or the intended use of these models name : 'techblog_202201' version : '1.0.0' config-version : 2 # This setting configures which "profile" dbt uses for this project. profile : 'sample' # These configurations specify where dbt should look for different types of files. # The `source-paths` config, for example, states that models in this project can be # found in the "models/" directory. You probably won't need to change these! source-paths : [ "models" ] analysis-paths : [ "analysis" ] test-paths : [ "tests" ] data-paths : [ "data" ] macro-paths : [ "macros" ] snapshot-paths : [ "snapshots" ] target-path : "target" # directory which will store compiled SQL files clean-targets : # directories to be removed by `dbt clean` - "target" - "dbt_modules" # Configuring models # Full documentation: https://docs.getdbt.com/docs/configuring-models # In this example config, we tell dbt to build all models in the example/ directory # as tables. These settings can be overridden in the individual model files # using the `{{ config(...) }}` macro. models : techblog_202201 : temp_table : +materialized : table +hours_to_expiration : 1 table : +materialized : table view : +materialized : view vars : base_table : bigquery-public-data.ml_datasets.census_adult_income index_col : id target_col : income_bracket list_agg : - avg - max - min - stddev macro macro/get_columns_list. sql BigQueryのINFORMATION_SCHEMAを利用してカラムの一覧を取得できるmacroを作成して利用しています。typesに型のリストを渡すことで、該当する型のカラムのみを取得することができます。 {% macro get_columns_list(table_name, types=None) -%} {% set columns_query %} select column_name from `{{table_name.dataset}}.INFORMATION_SCHEMA.COLUMNS` where table_schema = " {{table_name.dataset}} " and table_name = " {{table_name.name}} " {%- if types is not none %} and data_type in ( {%- for type in types %} {%- if loop . last %} " {{type}} " {%- else %} " {{type}} " , {%- endif %} {%- endfor %} ) {%- endif %} {% endset %} {% set results = run_query(columns_query) %} {% if execute %} {% set list_results = results.columns[ 0 ]. values () %} {% else %} {% set list_results = [] %} {% endif %} {{ return (list_results) }} {% endmacro %} models jinjaで書いたクエリ ↓ dbt compile で生成されたクエリ の順で紹介していきます。 models/view/row_census_adult_income. sql 元々のテー ブルデー タにindexとなるカラム id を追加しています。 select row_number() over ( order by 1 ) as {{var( " index_col " )}}, * from `{{var( " base_table " )}}` ↓ compile select row_number() over ( order by 1 ) as id, * from `bigquery- public -data.ml_datasets.census_adult_income` models/temp_table/ stg _census_adult_income. sql カテゴリ変数に対して以下の前処理を実施 空白削除 小文字化 正規化 目的変数( income_bracket ) 2値なので0,1に変換 *3 {%- set ref_table = " row_census_adult_income " %} {%- set list_numeric_columns = get_columns_list(ref(ref_table),types=[ " FLOAT64 " , " INT64 " ]) -%} {%- set list_categorical_columns = get_columns_list(ref(ref_table),types=[ " STRING " ]) -%} select {%- for col in list_numeric_columns %} {{col}}, {%- endfor %} {%- for col in list_categorical_columns %} {%- if col != var( " target_col " ) %} normalize( lower ( trim ({{col}})), NFKC) as {{col}}, {%- endif %} {%- endfor %} case when {{var( " target_col " )}} = " <=50K " then 1 else 0 end as {{var( " target_col " )}}, from {{ref(ref_table)}} ↓ compile select id, age, functional_weight, education_num, capital_gain, capital_loss, hours_per_week, normalize( lower ( trim (workclass)), NFKC) as workclass, normalize( lower ( trim (education)), NFKC) as education, normalize( lower ( trim (marital_status)), NFKC) as marital_status, normalize( lower ( trim (occupation)), NFKC) as occupation, normalize( lower ( trim (relationship)), NFKC) as relationship, normalize( lower ( trim (race)), NFKC) as race, normalize( lower ( trim (sex)), NFKC) as sex, normalize( lower ( trim (native_country)), NFKC) as native_country, case when income_bracket = " <=50K " then 1 else 0 end as income_bracket, from `buyma-analytics`.`techblog_202201_dev`.`row_census_adult_income` models/table/feature_census_adult_income. sql 量的変数 そのまま カテゴリ変数 dense_rank()で擬似的にLabel Encoding カテゴリ変数 x 量的変数 カテゴリ変数ごとに統計量(平均、最小、最大、 標準偏差 )を取得 {%- set ref_table = " stg_census_adult_income " -%} {%- set list_numeric_columns = get_columns_list(ref(ref_table),types=[ " FLOAT64 " , " INT64 " ]) -%} {%- set list_categorical_columns = get_columns_list(ref(ref_table),types=[ " STRING " ]) -%} select {%- for numeric_column in list_numeric_columns %} {%- if numeric_column != var( " target_col " ) %} {{numeric_column}}, {%- endif %} {%- endfor %} {%- for categorical_column in list_categorical_columns %} {%- set loop_index = loop .index0 + 1 %} dense_rank () over ( order by {{categorical_column}}) as {{categorical_column}}, {%- for numeric_column in list_numeric_columns %} {%- if numeric_column not in [var( " target_col " ), var( " index_col " )] %} {%- for agg in var( " list_agg " ) %} {{agg}}({{numeric_column}}) over (partition by {{categorical_column}}) as {{agg}} _ {{numeric_column}}_by_{{categorical_column}}, {%- endfor %} {%- endif %} {%- endfor %} {%- endfor %} {{var( " target_col " )}} from {{ref(ref_table)}} order by 1 ↓ compile select id, age, functional_weight, education_num, capital_gain, capital_loss, hours_per_week, dense_rank () over ( order by workclass) as workclass, avg (age) over (partition by workclass) as avg_age_by_workclass, max (age) over (partition by workclass) as max_age_by_workclass, min (age) over (partition by workclass) as min_age_by_workclass, stddev (age) over (partition by workclass) as stddev_age_by_workclass, avg (functional_weight) over (partition by workclass) as avg_functional_weight_by_workclass, max (functional_weight) over (partition by workclass) as max_functional_weight_by_workclass, min (functional_weight) over (partition by workclass) as min_functional_weight_by_workclass, stddev (functional_weight) over (partition by workclass) as stddev_functional_weight_by_workclass, avg (education_num) over (partition by workclass) as avg_education_num_by_workclass, max (education_num) over (partition by workclass) as max_education_num_by_workclass, min (education_num) over (partition by workclass) as min_education_num_by_workclass, stddev (education_num) over (partition by workclass) as stddev_education_num_by_workclass, avg (capital_gain) over (partition by workclass) as avg_capital_gain_by_workclass, max (capital_gain) over (partition by workclass) as max_capital_gain_by_workclass, min (capital_gain) over (partition by workclass) as min_capital_gain_by_workclass, stddev (capital_gain) over (partition by workclass) as stddev_capital_gain_by_workclass, avg (capital_loss) over (partition by workclass) as avg_capital_loss_by_workclass, max (capital_loss) over (partition by workclass) as max_capital_loss_by_workclass, min (capital_loss) over (partition by workclass) as min_capital_loss_by_workclass, stddev (capital_loss) over (partition by workclass) as stddev_capital_loss_by_workclass, avg (hours_per_week) over (partition by workclass) as avg_hours_per_week_by_workclass, max (hours_per_week) over (partition by workclass) as max_hours_per_week_by_workclass, min (hours_per_week) over (partition by workclass) as min_hours_per_week_by_workclass, stddev (hours_per_week) over (partition by workclass) as stddev_hours_per_week_by_workclass, dense_rank () over ( order by education) as education, avg (age) over (partition by education) as avg_age_by_education, max (age) over (partition by education) as max_age_by_education, min (age) over (partition by education) as min_age_by_education, stddev (age) over (partition by education) as stddev_age_by_education, avg (functional_weight) over (partition by education) as avg_functional_weight_by_education, max (functional_weight) over (partition by education) as max_functional_weight_by_education, min (functional_weight) over (partition by education) as min_functional_weight_by_education, stddev (functional_weight) over (partition by education) as stddev_functional_weight_by_education, avg (education_num) over (partition by education) as avg_education_num_by_education, max (education_num) over (partition by education) as max_education_num_by_education, min (education_num) over (partition by education) as min_education_num_by_education, stddev (education_num) over (partition by education) as stddev_education_num_by_education, avg (capital_gain) over (partition by education) as avg_capital_gain_by_education, max (capital_gain) over (partition by education) as max_capital_gain_by_education, min (capital_gain) over (partition by education) as min_capital_gain_by_education, stddev (capital_gain) over (partition by education) as stddev_capital_gain_by_education, avg (capital_loss) over (partition by education) as avg_capital_loss_by_education, max (capital_loss) over (partition by education) as max_capital_loss_by_education, min (capital_loss) over (partition by education) as min_capital_loss_by_education, stddev (capital_loss) over (partition by education) as stddev_capital_loss_by_education, avg (hours_per_week) over (partition by education) as avg_hours_per_week_by_education, max (hours_per_week) over (partition by education) as max_hours_per_week_by_education, min (hours_per_week) over (partition by education) as min_hours_per_week_by_education, stddev (hours_per_week) over (partition by education) as stddev_hours_per_week_by_education, dense_rank () over ( order by marital_status) as marital_status, avg (age) over (partition by marital_status) as avg_age_by_marital_status, max (age) over (partition by marital_status) as max_age_by_marital_status, min (age) over (partition by marital_status) as min_age_by_marital_status, stddev (age) over (partition by marital_status) as stddev_age_by_marital_status, avg (functional_weight) over (partition by marital_status) as avg_functional_weight_by_marital_status, max (functional_weight) over (partition by marital_status) as max_functional_weight_by_marital_status, min (functional_weight) over (partition by marital_status) as min_functional_weight_by_marital_status, stddev (functional_weight) over (partition by marital_status) as stddev_functional_weight_by_marital_status, avg (education_num) over (partition by marital_status) as avg_education_num_by_marital_status, max (education_num) over (partition by marital_status) as max_education_num_by_marital_status, min (education_num) over (partition by marital_status) as min_education_num_by_marital_status, stddev (education_num) over (partition by marital_status) as stddev_education_num_by_marital_status, avg (capital_gain) over (partition by marital_status) as avg_capital_gain_by_marital_status, max (capital_gain) over (partition by marital_status) as max_capital_gain_by_marital_status, min (capital_gain) over (partition by marital_status) as min_capital_gain_by_marital_status, stddev (capital_gain) over (partition by marital_status) as stddev_capital_gain_by_marital_status, avg (capital_loss) over (partition by marital_status) as avg_capital_loss_by_marital_status, max (capital_loss) over (partition by marital_status) as max_capital_loss_by_marital_status, min (capital_loss) over (partition by marital_status) as min_capital_loss_by_marital_status, stddev (capital_loss) over (partition by marital_status) as stddev_capital_loss_by_marital_status, avg (hours_per_week) over (partition by marital_status) as avg_hours_per_week_by_marital_status, max (hours_per_week) over (partition by marital_status) as max_hours_per_week_by_marital_status, min (hours_per_week) over (partition by marital_status) as min_hours_per_week_by_marital_status, stddev (hours_per_week) over (partition by marital_status) as stddev_hours_per_week_by_marital_status, dense_rank () over ( order by occupation) as occupation, avg (age) over (partition by occupation) as avg_age_by_occupation, max (age) over (partition by occupation) as max_age_by_occupation, min (age) over (partition by occupation) as min_age_by_occupation, stddev (age) over (partition by occupation) as stddev_age_by_occupation, avg (functional_weight) over (partition by occupation) as avg_functional_weight_by_occupation, max (functional_weight) over (partition by occupation) as max_functional_weight_by_occupation, min (functional_weight) over (partition by occupation) as min_functional_weight_by_occupation, stddev (functional_weight) over (partition by occupation) as stddev_functional_weight_by_occupation, avg (education_num) over (partition by occupation) as avg_education_num_by_occupation, max (education_num) over (partition by occupation) as max_education_num_by_occupation, min (education_num) over (partition by occupation) as min_education_num_by_occupation, stddev (education_num) over (partition by occupation) as stddev_education_num_by_occupation, avg (capital_gain) over (partition by occupation) as avg_capital_gain_by_occupation, max (capital_gain) over (partition by occupation) as max_capital_gain_by_occupation, min (capital_gain) over (partition by occupation) as min_capital_gain_by_occupation, stddev (capital_gain) over (partition by occupation) as stddev_capital_gain_by_occupation, avg (capital_loss) over (partition by occupation) as avg_capital_loss_by_occupation, max (capital_loss) over (partition by occupation) as max_capital_loss_by_occupation, min (capital_loss) over (partition by occupation) as min_capital_loss_by_occupation, stddev (capital_loss) over (partition by occupation) as stddev_capital_loss_by_occupation, avg (hours_per_week) over (partition by occupation) as avg_hours_per_week_by_occupation, max (hours_per_week) over (partition by occupation) as max_hours_per_week_by_occupation, min (hours_per_week) over (partition by occupation) as min_hours_per_week_by_occupation, stddev (hours_per_week) over (partition by occupation) as stddev_hours_per_week_by_occupation, dense_rank () over ( order by relationship) as relationship, avg (age) over (partition by relationship) as avg_age_by_relationship, max (age) over (partition by relationship) as max_age_by_relationship, min (age) over (partition by relationship) as min_age_by_relationship, stddev (age) over (partition by relationship) as stddev_age_by_relationship, avg (functional_weight) over (partition by relationship) as avg_functional_weight_by_relationship, max (functional_weight) over (partition by relationship) as max_functional_weight_by_relationship, min (functional_weight) over (partition by relationship) as min_functional_weight_by_relationship, stddev (functional_weight) over (partition by relationship) as stddev_functional_weight_by_relationship, avg (education_num) over (partition by relationship) as avg_education_num_by_relationship, max (education_num) over (partition by relationship) as max_education_num_by_relationship, min (education_num) over (partition by relationship) as min_education_num_by_relationship, stddev (education_num) over (partition by relationship) as stddev_education_num_by_relationship, avg (capital_gain) over (partition by relationship) as avg_capital_gain_by_relationship, max (capital_gain) over (partition by relationship) as max_capital_gain_by_relationship, min (capital_gain) over (partition by relationship) as min_capital_gain_by_relationship, stddev (capital_gain) over (partition by relationship) as stddev_capital_gain_by_relationship, avg (capital_loss) over (partition by relationship) as avg_capital_loss_by_relationship, max (capital_loss) over (partition by relationship) as max_capital_loss_by_relationship, min (capital_loss) over (partition by relationship) as min_capital_loss_by_relationship, stddev (capital_loss) over (partition by relationship) as stddev_capital_loss_by_relationship, avg (hours_per_week) over (partition by relationship) as avg_hours_per_week_by_relationship, max (hours_per_week) over (partition by relationship) as max_hours_per_week_by_relationship, min (hours_per_week) over (partition by relationship) as min_hours_per_week_by_relationship, stddev (hours_per_week) over (partition by relationship) as stddev_hours_per_week_by_relationship, dense_rank () over ( order by race) as race, avg (age) over (partition by race) as avg_age_by_race, max (age) over (partition by race) as max_age_by_race, min (age) over (partition by race) as min_age_by_race, stddev (age) over (partition by race) as stddev_age_by_race, avg (functional_weight) over (partition by race) as avg_functional_weight_by_race, max (functional_weight) over (partition by race) as max_functional_weight_by_race, min (functional_weight) over (partition by race) as min_functional_weight_by_race, stddev (functional_weight) over (partition by race) as stddev_functional_weight_by_race, avg (education_num) over (partition by race) as avg_education_num_by_race, max (education_num) over (partition by race) as max_education_num_by_race, min (education_num) over (partition by race) as min_education_num_by_race, stddev (education_num) over (partition by race) as stddev_education_num_by_race, avg (capital_gain) over (partition by race) as avg_capital_gain_by_race, max (capital_gain) over (partition by race) as max_capital_gain_by_race, min (capital_gain) over (partition by race) as min_capital_gain_by_race, stddev (capital_gain) over (partition by race) as stddev_capital_gain_by_race, avg (capital_loss) over (partition by race) as avg_capital_loss_by_race, max (capital_loss) over (partition by race) as max_capital_loss_by_race, min (capital_loss) over (partition by race) as min_capital_loss_by_race, stddev (capital_loss) over (partition by race) as stddev_capital_loss_by_race, avg (hours_per_week) over (partition by race) as avg_hours_per_week_by_race, max (hours_per_week) over (partition by race) as max_hours_per_week_by_race, min (hours_per_week) over (partition by race) as min_hours_per_week_by_race, stddev (hours_per_week) over (partition by race) as stddev_hours_per_week_by_race, dense_rank () over ( order by sex) as sex, avg (age) over (partition by sex) as avg_age_by_sex, max (age) over (partition by sex) as max_age_by_sex, min (age) over (partition by sex) as min_age_by_sex, stddev (age) over (partition by sex) as stddev_age_by_sex, avg (functional_weight) over (partition by sex) as avg_functional_weight_by_sex, max (functional_weight) over (partition by sex) as max_functional_weight_by_sex, min (functional_weight) over (partition by sex) as min_functional_weight_by_sex, stddev (functional_weight) over (partition by sex) as stddev_functional_weight_by_sex, avg (education_num) over (partition by sex) as avg_education_num_by_sex, max (education_num) over (partition by sex) as max_education_num_by_sex, min (education_num) over (partition by sex) as min_education_num_by_sex, stddev (education_num) over (partition by sex) as stddev_education_num_by_sex, avg (capital_gain) over (partition by sex) as avg_capital_gain_by_sex, max (capital_gain) over (partition by sex) as max_capital_gain_by_sex, min (capital_gain) over (partition by sex) as min_capital_gain_by_sex, stddev (capital_gain) over (partition by sex) as stddev_capital_gain_by_sex, avg (capital_loss) over (partition by sex) as avg_capital_loss_by_sex, max (capital_loss) over (partition by sex) as max_capital_loss_by_sex, min (capital_loss) over (partition by sex) as min_capital_loss_by_sex, stddev (capital_loss) over (partition by sex) as stddev_capital_loss_by_sex, avg (hours_per_week) over (partition by sex) as avg_hours_per_week_by_sex, max (hours_per_week) over (partition by sex) as max_hours_per_week_by_sex, min (hours_per_week) over (partition by sex) as min_hours_per_week_by_sex, stddev (hours_per_week) over (partition by sex) as stddev_hours_per_week_by_sex, dense_rank () over ( order by native_country) as native_country, avg (age) over (partition by native_country) as avg_age_by_native_country, max (age) over (partition by native_country) as max_age_by_native_country, min (age) over (partition by native_country) as min_age_by_native_country, stddev (age) over (partition by native_country) as stddev_age_by_native_country, avg (functional_weight) over (partition by native_country) as avg_functional_weight_by_native_country, max (functional_weight) over (partition by native_country) as max_functional_weight_by_native_country, min (functional_weight) over (partition by native_country) as min_functional_weight_by_native_country, stddev (functional_weight) over (partition by native_country) as stddev_functional_weight_by_native_country, avg (education_num) over (partition by native_country) as avg_education_num_by_native_country, max (education_num) over (partition by native_country) as max_education_num_by_native_country, min (education_num) over (partition by native_country) as min_education_num_by_native_country, stddev (education_num) over (partition by native_country) as stddev_education_num_by_native_country, avg (capital_gain) over (partition by native_country) as avg_capital_gain_by_native_country, max (capital_gain) over (partition by native_country) as max_capital_gain_by_native_country, min (capital_gain) over (partition by native_country) as min_capital_gain_by_native_country, stddev (capital_gain) over (partition by native_country) as stddev_capital_gain_by_native_country, avg (capital_loss) over (partition by native_country) as avg_capital_loss_by_native_country, max (capital_loss) over (partition by native_country) as max_capital_loss_by_native_country, min (capital_loss) over (partition by native_country) as min_capital_loss_by_native_country, stddev (capital_loss) over (partition by native_country) as stddev_capital_loss_by_native_country, avg (hours_per_week) over (partition by native_country) as avg_hours_per_week_by_native_country, max (hours_per_week) over (partition by native_country) as max_hours_per_week_by_native_country, min (hours_per_week) over (partition by native_country) as min_hours_per_week_by_native_country, stddev (hours_per_week) over (partition by native_country) as stddev_hours_per_week_by_native_country, income_bracket from `buyma-analytics`.`techblog_202201_dev`.`stg_census_adult_income` order by 1 このようにして、221個の特徴量を生成することができました。 Appendix:packageの利用 dbtには package というライブラリのようなものがあります。 *4 例えば、dbt-utilsには、 get_column_values というカラムのユニークな値のリストを取得することができます。one-hot encodingを行いたい場合は、下記のように書くことができます。 *5 {%- set categorical_column = " sex " -%} {%- set ref_table = " stg_census_adult_income " -%} {%- set unique_values = dbt_utils.get_column_values(ref(ref_table), categorical_column) -%} select {%- for value in unique_values -%} case when {{categorical_column}} = " {{value}} " then 1 else 0 end as {{categorical_column}} _ {{value}}, {%- endfor %} from {{ref(ref_table)}} ↓ compile select case when sex = " male " then 1 else 0 end as sex_male, case when sex = " female " then 1 else 0 end as sex_female, from `your-project`.`your_dataset`.`stg_census_adult_income` まとめ 個人で使っているレベルですが、 SQL をエディターで書いていた時より効率よくクエリを作成することができとても便利に感じています。今回紹介しきれなかった test や docs なども業務では活用しています。今後はBigQueryMLと組み合わせて、前処理〜モデルの学習・推論までを全てdbt x BigQueryで完結させられたらなと考えています。 *6 株式会社 エニグモ 正社員の求人一覧 hrmos.co *1 : めちゃくちゃ多段のwith句を使っていることが多いです。 *2 : dbtの環境構築方法は丁寧に紹介くださっている記事がたくさんあるので割愛します。 *3 : ここも汎用的な処理にしたかったのですが手を抜きました。 *4 : packageの導入方法については割愛します。 *5 : BigQueryの カラム名 として使えない文字列が入っているとエラーになるので要注意です。 *6 : dbt-ml というBigQueryMLを実行するpackageがあります。
アバター
こんにちは、人事総務グループの 廣島 です。 エニグモ で中途・新卒採用、採用広報などを担当しています。 エニグモ は「世界を変える、新しい流れを。」をミッションに、世界166ヶ国に900万人以上の会員を擁するソーシャルショッピングサイト「 BUYMA 」を運営しています。 今回は、エンジニア部署の部長小澤さんのインタビューをお届けします。 エンジニア組織や開発体制、 エニグモ のカルチャーなどについて伺いました。 ※この記事は Enigmo Advent Calendar 2021 の25日目の記事です。 あっというまで アドベントカレンダー も最終日です!   目次 まずは簡単に経歴や自己紹介をお願いします エンジニア組織や開発体制について 現在のエンジニアの開発組織について教えて下さい 小澤さんが入社してから今までで組織はどのように変わりましたか? データテクノロジーグループはここ数年で新しくできた組織ですが、グループ立ち上げの経緯を教えて下さい 開発体制の特徴は? 機能別の開発体制に移行した背景やその後の変化はありましたか? BUYMAの開発を行う魅力は? 開発や組織の課題について エンジニア採用で大切にしていることや、活躍するメンバーとは エンジニア採用で大切にしていることはありますか? エンジニアの雰囲気や活躍するメンバーの特徴は? エニグモの技術選定について エンジニアのキャリアについて どのようなキャリアアップの選択肢がありますか? マネージャーへはどのようにアサインされるのでしょうか? フロントエンドエンジニア、サーバーサイドエンジニアを明確に分けていないのも特徴ですよね 部長として大切にしていることはありますか?   まずは簡単に経歴や自己紹介をお願いします 前職は新卒で入社した SIer で勤怠管理や人材管理などのパッケージシステムの開発をしていました。会社の中でも比較的開発を担当できる部署でしたが、役職が上がると開発から離れ管理がメイン(電話片手にエクセルとにらめっこみたいな)となる為、開発に関わりつづけたい、Webに行きたいと思い転職を決意し、ご縁があり エニグモ に入社しました。 入社してかれこれ10年が経ち、現在エンジニアの部長を務めています。 エンジニア組織や開発体制について 現在のエンジニアの開発組織について教えて下さい 現在、エンジニアの組織は、業務委託として参画してくださっている方も含め約50名の組織となっており、4つのグループ(インフラグループ、データテク ノロ ジー グループ、アプリケーション開発グループ、グローバルグループ)に分かれています。 小澤さんが入社してから今までで組織はどのように変わりましたか? 入社した当時、エンジニア組織としては8人程でしたので、それから比べると組織はだいぶ大きくなりましたね。私が部長になってからは、社員15人くらいまでは部長以下の役職を設けず全員フラットな組織でしたが、組織の拡大とともに、各グループにマネージャーの役職を置き、現在の組織体制となりました。 データテク ノロ ジー グループはここ数年で新しくできた組織ですが、グループ立ち上げの経緯を教えて下さい データテク ノロ ジー グループは元々アプリケーション開発グループの一部で、性能改善やバックエンドの安定化などを担うチームでした。全社的にデータドリブンな環境が加速していく中で、データ収集や 機械学習 、検索性向上などのデータ領域に対して専門性の高いチームへ進化していき、今のデータテク ノロ ジー グループへとなりました。 これらの進化は会社側からの トップダウン の方針ではなく、現場のマネージャーからの ボトムアップ で組織が進化していった経緯があります。 開発体制の特徴は? 現在、大きく3つの機能(購入者向け機能、出品者向け機能、サービスインフラ)別チームに分かれて開発案件に対応しています。各機能別にエンジニア、デザイナー、ディレクター、データアナリスト、ビジネスサイド(CS・MD等)が組織横断でプロジェクトに アサイ ンされ開発を進める、機能別の開発体制になっています。 職種で役割を完全に分けてしまうのではなく、企画・設計段階からみんなで意見を出し合い、サービスや機能を作っていくのが特徴です。 機能別の開発体制に移行した背景やその後の変化はありましたか? 元々はプロジェクトへのメンバーの アサイ ンは、プロジェクトの難易度や特性とメンバーの得意分野、やりたいこと、稼働等を見て判断しておりました。 しかし、 BUYMA は1つのサービスとして成り立つために様々な機能が組み合わさっています。 例えば、購入者向けの機能(商品詳細ページやレコメンド、クーポン等)と出品者向け機能(出品管理・ショップ連携・お問い合わせ管理等)ではサービスの性質・要件や抱える課題は異なり、仕様は複雑になっているため、開発メンバー全員が全ての機能の特性をキャッチアップすることは難しくなっていました。 そこで、開発メンバーが機能( ドメイン )別に特化することで、専門性を持ち効果的にスピードを上げて案件に対応できるのではないかと考え、機能別開発体制となりました。 開発体制の変更によって、機能ごとにエンジニア一人一人が当事者意識を持って仕事に取り組むことができ、エンジニアもプロダクト開発の目標設定への責任感・コミット力があがったと思います。 BUYMA の開発を行う魅力は? まずは、 BUYMA というそれなりのユーザーがいる ECサイト ・大規模サービスに関われることでしょうか。やった事への影響も大きいですし、画面を変えれば良くなったとユーザーから褒めていただいたり、時には厳しいご指摘をいただくなど反応もありますし、 トラフィック もあるのでパフォーマンスチューニングのやりがいもあります。また、ローンチして17年を超える Webサービス なので、古いシステム・技術もあり直すことも多いので、そういうところが好きな方であればやりがいに感じる方もいるかと思います。 開発や組織の課題について 前段でもお話ししましたが、17年を超える Webサービス の為、古いシステムやレガシーな技術もいっぱいあるところですね(新しい技術もどんどん導入していますが)。リフレッシュしないと開発速度が落ちてしまうので、常に技術のアップデートは行う必要があります。 技術のアップデートをするにも、機能やデータベース等が複雑に絡み合っている為、1つの技術を変えると他の機能への影響範囲も大きいため、それぞれチューニングが必要です。そのあたり開発・運用サイクルを効率よく回すためにも、いくつかの改善案を検討しています。 上記のような課題もある為、新しいサービスや機能を開発したいというエンジニアだけでなく、レガシーな技術をモダン化したい、開発が上手くまわる仕組みや環境を作りたい・整えたいというエンジニアも組織全体としてスピードアップして開発する為には必要であり、活躍の機会があります。 エンジニア採用で大切にしていることや、活躍するメンバーとは エンジニア採用で大切にしていることはありますか? 新メンバーが入社した際にも、その人がやりたいことをやる方がきっといいと思っているので、「こういうことがやりたい」というモチベーションが高く、且つ自走力があり実行できる人がいいですね。実は、私も上からあれしろこれしろとあまり言われないので、エンジニアにもあれしろこれしろ言わないようにしています。やりたい人に任せることが一番いい結果を生むと感じています。 また、成長意欲の高い人や向学心の強い方にはオススメな環境です。 なぜなら、 BUYMA はさまざまな機能が絡み合うため、システムとして非常に複雑になっています。新メンバーが、特に若手メンバーがシステムの概要をキャッチアップ・理解するまでに時間がかかり若手に与える適切な課題・タスクを切り分けるのが難しい場面もあります。 「教科書で学んできたことが教科書通りにはいかず、開発する上であれもこれも詰め込まれるので、一気に10人くらいに殴られる感覚になる」と、ある若手メンバーが言っていたのが印象的です。 1つ1つ少しずつ成長したい人にとっては、最初は我慢が必要かもしれません。その分成長のスピードは早く、 BUYMA で開発ができればだいたい何でも開発できるようになると思うので、臆せずチャレンジしていただきたいですね。 エンジニアの雰囲気や活躍するメンバーの特徴は? 色々なキャ ラク ターの人がいますが共通項としては、みんなまじめで何事も一生懸命な頑張り屋が多い印象です。また、エンジニアに関わらず、 エニグモ のメンバーはいい人が多いと思います。 活躍するメンバーの特徴は、ア イデア がある人じゃないでしょうか。ア イデア というのは、新規の提案だけなく、定例や MTG 内でも自分の意見や考えを発言し、発言して終わりではなく行動が伴う方です。そういったメンバーが周囲からも信頼を得られ活躍しているように感じます。 エニグモ の技術選定について メンバーやマネージャーからの ボトムアップ で決まる場合が多いですね。 経営判断 や、エンジニアのマネージャー会議等で、これを導入していこうというよりは、 専門性のある各メンバーが今の課題の中からこの技術が良いのでないかと判断し導入します。 もちろん例外もあります。規模感にもよりますが、さすがに BUYMA 全体の言語を PHP から Ruby へ変更しようとなった時や、 BUYMA のインフラ環境をオンプレから AWS へ移行等の規模が大きい案件の場合は役員プレゼンし承認を得ています。そのほか、技術導入による影響範囲が大きい場合や、導入にお金が絡む際も私のところに相談が来ますね。 エンジニアのキャリアについて どのようなキャリアアップの選択肢がありますか? スペシャ リストとしてもマネージメントとしてもキャリアアップの選択の機会があります。 役職や肩書がなくても スペシャ リストとして高い報酬が得られる給与体系となっており、本人の志向や経験スキルに応じて柔軟なキャリアステップを歩んでいただけます。 マネージャーへはどのように アサイ ンされるのでしょうか? エニグモ のエンジニアの特徴としては、チームを作りたいという人よりも、 スペシャ リストになりたい人の方が多いように感じます。その為、マネージャーを任せたいメンバーには1on1等でやってみる?と聞いています。 一概には言えませんが、任せたいと思うエンジニアはすでにメンバーの役割を超えて、チームをまとめていたり、PM・リーダーとしての動きをしているので、そこからマネージャーをやってみようかとなるパターンも多いように感じます。意図的というよりも自然とそうなっています。 フロントエンドエンジニア、サーバーサイドエンジニアを明確に分けていないのも特徴ですよね はい。 エニグモ ではエンジニアの担当領域をフロントエンド、バックエンドではなく、サービス・プロジェクト単位で アサイ ンしている為、 フルスタ ックな知識・経験やスキルをつけることが出来ます。 なぜ分けていないかというと、フロントエンド・バックエンド両方できた方がやりがいや完成した際の達成感があると考えているためです。 せっかく画面があるサービスなので裏側から表に出てくるまでの一連の流れをやった方が楽しいと思うため、やりたいメンバーにはサーバーサイド、フロントエンドとわずにプロジェクトに アサイ ンし任せています。 部長として大切にしていることはありますか? みんなが楽( ラク )になればよいなと思っています。楽というのは業務が上手くまわる感じですね。 1on1でも、今の仕事は楽しいか、何をやりたいのかを聞くようにしています。何が好きで何がやりたいかを聞いてそれを実現できる環境を作ることが今の私の役割だと思ってます。 経営陣もエンジニア組織のみならず、各部門の意思を最大限尊重してくれる文化なので、部長としてもやりやすい環境だと思っています。   以上、エンジニア部長の小澤さんのインタビューでした! こちらで、 Enigmo Advent Calendar 2021 は以上となります。今年も色々な記事がありましたね。 2022年もよろしくお願いします! 株式会社 エニグモ 正社員の求人一覧 hrmos.co
アバター
こんにちは、インフラエンジニア の 加藤( @kuromitsu_ka )です。 この記事は Enigmo Advent Calendar 2021 の 24 日目の記事です。 今回は、オンプレミスの MySQL を、Auroraへ移行する際、困ったことと対応したことを記載します。 移行方式をざっくりいうと、オンプレミスの MySQL より取得した、論理バックアップ(mysqldump)とバイナリログを使用してAuroraへ移行しました。移行のため、リストア環境と、リストア後のデータのチェック環境を構築したので先にその説明を記載して、困ったところと対応を記載していきます。 リストア環境 オンプレミス MySQL サーバから取得した、バックアップファイルをAuroraへ適用する環境を作りました。EC2の MySQL と、Auroraとで レプリケーション を貼り、EC2の MySQL にデータを投入してリストアしました。バックアップファイルのダウンロードや、リストアジョブの スクリプト 実行は、リストアジョブサーバから実行します。 リストア後のデータのチェック環境 Auroraへのデータのリストア後に、データの差分確認をする環境として Memcached も用意しました。それぞれのテーブルのデータの合計 チェックサム 値を Memcached に入れて比較しました。こちらも、チェックのジョブ スクリプト 実行は、リストアジョブサーバから実行します。 困ったこといくつか。 本題のAuroraへの移行で困ったことは、5個あり、順を追って記載します。 やんごとなき理由で、Auroraへのリストアに物理バックアップが使えず困った。 Auroraの仕様上、バイナリログを直接適用できずに困った。 Auroraへのリストアで、バイナリログの適用に2日間もかかって困った。 リストア環境を作ったものの、バイナリのログ適用がコケて困った。 リストア後、バイナリログ適用したデータの時間がなぜか9時間ずれて困った。 困ったこと その1 やんごとなき理由で、Auroraへのリストアに物理バックアップが使えず困った。 Auroraへのリストアには、物理バックアップが、サポートされていました。こちらが使えると安心だったのですが、問題があり使用できませんでした。結果的に論理バックアップから、リストアすることとなりました。 公式ドキュメント 外部の MySQL データベースから Amazon Aurora MySQL DB クラスターへのデータ移行 物理リストアできなかった原因 オンプレミスの MySQL には、パラメータ( innodb_undo_tablespace )が設定されていました。Auroraでは、こちらが変更 不能 になっているため、物理バックアップでは、Auroraへのリストアでコケていました。パラメータ変更には、 MySQL サービスの再起動が必要なため、論理バックアップを使用することとなりました。 エラーログ Auroraのerror/ mysql -error-running.log、error/ mysql -error.logより確認したエラーログ [ERROR] InnoDB: Unable to open undo tablespace './/undo001'. [ERROR] InnoDB: Plugin initialization aborted with error Generic error [ERROR] Plugin 'InnoDB' init function returned error. [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed. [ERROR] Failed to initialize builtin plugins. [ERROR] Aborting 問題のパラメータ undo ログが分割するテーブルスペース数を設定するパラメータ( innodb_undo_tablespace ) 物理リストアできなかった値 - innodb_undo_tablespace = 2 物理リストアできる値 + innodb_undo_tablespace = 0 公式ドキュメント Aurora MySQL 設定パラメータ パラメータ名 変更可能 innodb _undo_tablespace いいえ 困ったこと その2 Auroraの仕様上、バイナリログを直接適用できずに困った。 Auroraへは、直接バイナリログ適用できない仕様で、リストア環境を用意することになりました。BINLOGコマンドは、スーパーユーザーの権限での実行が必要なのですが、Aurora では、スーパーユーザー権限を利用することはできないそうで、コケてしまいました。そのため、リストア環境としては、EC2に MySQL を作成して、 レプリケーション を貼りました。 ※他社事例では、バイナリログをデコードして生のクエリを直接Auroraに適用する方法もありましたが、移行作業をしていた当時は見ていませんでした...。 適用コマンド # mysqlbinlog --no-defaults --database=${DB_NAME} --start-datetime=${START_TIME} --stop-datetime=${STOP_TIME} bin-log.00xxx | mysql -h ${AURORA_ENDPOINT} -P 3306 -u admin -p エラーログ ERROR 1227 (42000) at line 7: Access denied; you need (at least one of) the SUPER privilege(s) for this operation 公式ドキュメント 13.7.6.1 BINLOG Statement Amazon Aurora MySQL DB クラスターと外部の MySQL データベースを同期する 外部のソースインスタンスを使用したバイナリログファイル位置のレプリケーションの設定 困ったこと その3 Auroraへのリストアで、バイナリログの適用に時間がかかって困った。 Auroraへのリストアでは、バイナリログ適用(1桁GB程度でも)に2日間ほどかかりました。こちらは、AuroraのDB インスタンス のマシンリソースの増強と、Auroraのパラメータを変更したところ高速化できました。結果、バイナリログ適用は、20分程度に納まるようにできました。 対応したこと AuroraのCPU、メモリ使用率を見つつ インスタンス タイプを変更 サーバ インスタンス タイプ CPU メモリ(GB) オンプレミス MySQL 12 252 EC2の MySQL (リソース増強前) db.t3.small 2 2 EC2の MySQL (リソース増強後) db.r6g.4xlarge 16 128 Auroraのパラメータより、バイナリログ出力を一時的にOFFに変更 Binlog_format = OFF 公式ドキュメント Amazon RDS インスタンスタイプ MySQL または別の Aurora DB クラスターとのレプリケーションの設定 困ったこと その4 リストア環境を作ったものの、バイナリのログ適用がコケて困った。 バイナリログ適用が タイムアウト したり、2,3割適用できたところでコネクションエラーになったりもしました。 デバッグ の際も、 タイムアウト とコネクションエラーしか出ず、原因がぱっと見ではわからずで困りました。結果として、リストア環境の MySQL パラメータを変更することで、解決しました。 エラーログ ERROR -- : Lost connection to MySQL server during query (Mysql2::Error::ConnectionError) MySQL パラメータ変更 リストア環境のEC2の MySQL で、パラメータチューニングを行いました。ひとまず、 タイムアウト 関係のパラメータを操作しましたが、一部バイナリログデータを適用できず止まってしまうものもありました。続いて、パケットサイズのパラメータも変更しましたが、エラーは続いていました。最終的に、プロセスが、十分なメモリを確保できていないのかなと考えて、メモリキャッシュ関係のパラメータを変更して解決しました。 タイムアウト 関係の MySQL パラメータ(ひとまず2日間様子見できるように設定) connect_timeout = 172800 net_write_timeout = 172800 net_read_timeout = 172800 wait_timeout = 172800 interactive_timeout = 172800 パケットの最大サイズの MySQL パラメータ(最大値に設定) max_allowed_packet = 1073741824 メモリキャッシュ関係のパラメータ(最大値に設定) ※変更したパラメータ「 innodb_buffer_pool_size 」は、読み込み、書き込みのパフォーマンス向上にも使われるパラメータです。 innodb_buffer_pool_size = 1G 困ったこと その5 リストア後、バイナリログ適用したデータのうち、タイムスタンプ関係のデータで、なぜか9時間ズレが発生して困った。 バイナリログで適用したデータのみ、何故か9時間ズレるという問題も経験しました。 MySQL の タイムゾーン 関係の、パラメータを変更して対応しましたが、解消しませんでした。結果として、バックアップ取得元の、バイナリログフォーマットのパラメータを変更して解決しました。 タイムゾーン のパラメータ オンプレミスの MySQL も、Auroraも、リストア環境のEC2の MySQL も JST に設定していました。 バイナリログ取得元の MySQL と、EC2に作成した MySQL のパラメータ +------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | JST | | time_zone | SYSTEM | +------------------+--------+ Auroraのパラメータ Auroraの場合、パラメータ「time_zone」が、データベースのデフォルト値になります。 +------------------+------------+ | Variable_name | Value | +------------------+------------+ | system_time_zone | UTC | | time_zone | Asia/Tokyo | +------------------+------------+ バイナリログフォーマットのパラメータ 「 binlog_format 」をMIXEDから、ROW変更 レプリケーション 元の binlog_format を ROW とすることで、バイナリログ適用の際、クエリでなく実行結果が レプリケーション されるようになるそうでした。バックアップ取得元の、オンプレミスの MySQL にてパラメータ変更した結果、無事、データが一致しました。 バイナリログを適用した、データのタイムスタンプのデータが9時間ズレたパラメータ - binlog_format = 'MIXED' バイナリログを適用した、データで時間のズレがなかったパラメータ + binlog_format = 'ROW' ドキュメント Amazon Aurora DB クラスターのタイムゾーンを変更するにはどうすればよいですか? 感想 オンプレミス MySQL のAurora移行は、たいへんでしたが楽しかったです。 明日の記事の担当は 人事総務 の 廣島 さんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは。サーバーサイドを担当している橋本です。 この記事は Enigmo Advent Calendar 2021 の23日目の記事です。 普段は Ruby on Rails を書くことが多いですが、とあるプロジェクトでAirflowを使った既存バッチの性能改善を行いました。プロジェクトはAirflowについて全く知らない状態からスタートして学ぶことが多かったので、この記事でAirflowの紹介と開発の感想を書いていきたいと思います。 Airflowとは? Airflowとはワークフローの管理ツールで、あるタスクを実行したら次のタスクを実行するといった形で一連のタスクを管理するものになります。Airflowではjobの実行順や依存関係をDAGで定義していて、DAG自体は Python で作成されています。 簡単にDAGのコードのご紹介です。(環境構築は省略させていただきます。) 下記のプログラムは 文字列をprintするタスクを順番に実行するものです。 DAG で実行時刻やDAGの名前の設定をします。 PythonOperator を使ってタスクを定義し、一番下の行の execute_task1 >> execute_task2 で実行するタスクの順番を定義します。 import airflow from airflow.operators.python_operator import PythonOperator from airflow.models import DAG from datetime import datetime args = { 'owner' : 'airflow' } dag = DAG( dag_id = 'advent_calendar_tasks' , default_args = args, schedule_interval = '0 0 * * *' ) def task1 (): print ( 'task1' ) def task2 (): print ( 'task2' ) execute_task1 = PythonOperator( task_id = 'execute_task1' , retries = 2 , python_callable = task1, dag = dag) execute_task2 = PythonOperator( task_id = 'execute_task2' , retries = 2 , python_callable = task2, dag = dag) execute_task1 >> execute_task2 管理画面にアクセスするとDAGの情報を見ることができ、実際に実行することもできます。 使ってみてよかったこと モニタリングが GUI から管理できる DAGのスケジュールや実行状況を同一画面で見ることができるので、モニタリングがしやすかったです。また、DAGの実行やスケジュールの on/offの設定も管理画面からできるので、コマンドを実行する手間を減らすことができたのが便利でした。 タスク単位で確認できる 管理画面のDAGの詳細画面ではタスクを個別で見ることができます。プログラム作成時にタスクを上手く分割すればDAGが実行するタスクを簡単に確認できます。 タスク実行に便利なライブラリが提供されている 実際のタスクでは SQL を実行したりファイルをアップロードする必要があると思いますが、DAGで使用できるライブラリが提供されています。プロジェクトでは FTP をアップロードするために FTPHook というライブラリを使いましたが、 Python で標準で使える ftplib よりも簡単に FTP を扱うことができました。 使ってみて難しかったこと Python に不慣れだったこと プロジェクトでは Ruby on Rails => Python へ刷新したのですが、普段は Ruby を書いているので Python に慣れるのが大変でした。 セキュアな情報の設定が初見だとわかりにくかったこと Airflowではセキュアな情報をConnectionsで管理していますが、最初はDAGに接続情報を書いてしまいました。あらかじめドキュメントを読んでAirflowのコンセプトを理解する必要があると感じました。 まとめ ここまでAirflowの紹介と感想を書きましたが、開発中は学ぶことが多く新鮮な気持ちで開発ができました。 明日の記事の担当は インフラエンジニアの加藤さんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、サーバーサイドエンジニアの Steven です。 この記事は Enigmo Advent Calendar 2021 の22日目の記事です。 今回は Vagrant 環境をリプレースすることとなった Docker 環境をどう早くしたかについて説明します。 スタート地点は Vagrant 環境 エニグモ では以前から VirtualBox と Vagrant によるローカル環境を使って、開発してました。 使い勝手は完璧ではなかったのですが、開発する分には問題がとくになく長年活用されました。 ただし、それは構築ができたらの話で、構築時間が長いのと、時間が立てば立つほど自ずと新しい構築エラーが発生して、随時対応しないといけない状態でした。 エンジニアの場合、超えられない問題ではなかったのですが、デザイナーなどテクニカルな知識がそれほどない方だと、サポートしてもハードルがかなり高かったです。 VM 内で使っていた OS も古いバージョンの CentOS で、いずれ更新しないといけなかったです。 Docker 環境ができました Docker で新しいローカル環境を作ることで以上の問題を解消できないかと動いてくださったエンジニアがいました。 そうすれば、構築時間の短縮と、安定性の改善、使い勝手の向上を実現できるからです。 構築して想定通り改善はできましたが、代わりに新しい問題が現れました。 それもよくあるパターンのようで、 Vagrant 環境よりパフォーマンスが悪く、使い物にならない環境になってしまいました。 ローカル環境とはいえデータセンターにあるサーバーとつなげたりするので、もともとの Vagrant 環境でもはじめからそれほど早くはなかったです。 なので、それ以上パフォーマンスが落ちると、対策が必須となってしまいます。 当時はチューニングを試みましたが、根本的な改善が見られず、Docker 環境の導入は一旦保留となりました。 レスポンスタイム比較 ページ Docker環境 Vagrant 環境 トップ 9.95s 1.07s 検索結果 9.22s 1.89s マイページ 9.30s 1.79s Docker Desktop for Mac について この場合 Docker 環境のパフォーマンスが悪かったのはコンテナーと macOS 間のファイル IO のパフォーマンスが悪かったからです。 アプリケーションコードをすべてメ モリー に保持するなど、ファイル IO がそれほど発生しないアプリケーションの場合は問題にならないこともあると思いますが、私達の場合はファイル IO がどうしても多く発生する環境なので、必然的にパフォーマンスが悪かったです。 Docker Desktop for Mac では、 Linux 環境と違って、コンテナーはそのまま macOS の カーネル に実行されておらず、 macOS 上で動く VM の中にある Linux カーネル によって実行されています。 なぜそうなっているかというと、 macOS の カーネル ではコンテナー化のサポートがなくて docker のようなコンテナーを実装することができないからです。 なので、bind ボリュームを通してコンテナー内から macOS 側にあるファイルにアクセスする時は、 VM の中から osxfs(レガシー)か gRPC FUSE という ファイルシステム レイヤーを通して、 macOS 側のファイルが読み込まれます。 ただし、抽象化が多いところから、ケースによってそのレイヤーがかなり遅くて、ネイティブのアプリケーションと比べ物にならないことが珍しくないです(当然といえば当然ですが)。 Docker Desktop for Mac でキャッシュのオプションもありますが、試した結果それほど影響が大きくなくて、違いに気づけるかどうかというレベルでした。 Docker チームでパフォーマンスの問題を認識していて、改善を以前から試していますが、 ファイルシステム の実装はかなり難しいもので、 トレードオフ が多いです。 パフォーマンスを高くするために工夫すると、整合性などの面で新しい問題が現れたりします。 パフォーマンスは改善傾向にありますが、満足の行かないケースがまだ多いと思います。 Mutagen とは Docker Desktop が提供するオプションだけでは解決できない問題なので、 サードパーティー による解決策を探しました。 最初は docker-sync を試しましたが、最終的に Mutagen に落ち着きました。 Mutagen はファイル同期とネットワークの フォワ ーディングのためのツールで、本来は クラウド にあるリソースをローカル環境で使うためのものかと思いますが、最近は docker compose のサポート が追加されて、docker 環境と合わせて使うことが可能になりました。 ファイル同期は rsync によるものなので、パフォーマンスがよくて、かなり堅牢なものです。 docker compose と合わせて使う場合は macOS とコンテナーの間に、 VM 内に同期されているファイルのコピーが用意されます。 コンテナ内から本来 bind であったボリュームへのファイルアクセスが発生した場合は macOS 側のファイルを読みに行かず、 VM のファイルにのみアクセスするようになります。 アプリケーションのファイル処理が VM 内で完結するため、 macOS と VM 間のファイルのやり取りが激減して、パフォーマンスの ボトルネック がなくなります。 Docker チームでも Docker Desktop に Mutagen を正式的に導入する動きが以前ありましたが、導入で追加の複雑さが生じることから、やめることとなったようです。その代わりに gRPC FUSE を優先するようになりました。 導入例 Mutagen の導入はかなり簡単です。 まずは brew で Mutagen をインストールします。 $ brew install mutagen-io/mutagen/mutagen-beta # 現在はβバージョンが必要です 続いて、 docker-compose.yml で macOS 側のファイルにアクセスするためのボリュームを用意します。 services : bm_on_rails : # ... volumes : - rails-source-sync:/bm_on_rails bm_php : # ... volumes : - php-source-sync:/home/web/bm_php volumes : rails-source-sync : php-source-sync : 最後に、同じファイルで、 x-mutagen の項目の配下に Mutagen の設定を指定します。 x-mutagen : sync : rails-source-sync : mode : 'two-way-resolved' alpha : './volumes/bm_on_rails' beta : 'volume://rails-source-sync' php-source-sync : mode : 'two-way-resolved' alpha : './volumes/bm_php' beta : 'volume://php-source-sync' alpha と beta は同期のエンドポイントとなります。 意味合いは mode によりますが、以上では alpha は macOS 側のパス、 beta は Docker のボリュームを指しています。 mode にはいくつかの選択肢がありますが、コンフリクトを自動解消するとして、 alpha の変更をどんな時も優先したい場合は two-way-resolved が適切です。 詳しくは こちら をご確認ください。 セットアップができたら、次は Docker 環境を mutagen compose up で立ち上げます(Mutagen の新しいバージョンでは mutagen-compose up )。 docker compose コマンドを使うと、Mutagen の処理がスキップされるので、間違えないよう注意してください。 ただのラッパーなので、 docker compose でできることは mutagen compose でもできるはずです。 ちょっと不便かもしれませんが、Mutagen の開発者側で docker compose をそのまま使えるように検討されているようです。 環境の初回起動に macOS 側のファイルが VM 内にコピーされるので、ファイルの量によって時間がかなりかかってしまう可能性があります(私達の場合は 10分ぐらい)が、二回目以降は Mutagen を使ってないのとあまり変わらなくなります。 導入後、アプリケーションのパフォーマンスは Vagrant 環境よりやや早くなりました。 データセンターへのアクセスがどうしても発生するので、そのパフォーマンスで目標を達成としました。 注意 Mutagen は Docker Desktop のバージョンに依存していますので、Mutagen のバージョンと Docker Desktop のバージョンに気をつけてください。 Docker Desktop のアップデートが来る度にすぐアップデートすると、Mutagen が動かなくなってしまう恐れがあります。 Mutagen の docker compose サポートはまだβですが、バグがほぼなくとても安定しています。 調整 デフォルトで macOS 側のファイルすべてが VM 内に同期されるので、 .git ディレクト リなど VCS 用のファイルを同期したくない場合は追加の設定が必要となります。 任意のファイルの同期をスキップすることも可能です。詳しくは こちら をご参照ください。 x-mutagen : sync : defaults : ignore : vcs : true # ... 特に設定がない状態では Mutagen に同期されているファイルのオーナーとグループ、 パーミッション はコンテナー内でデフォルトなものとなってしまいます(オーナーとグループはおそらく root となります)。実行権限のみ同期されます。 なので、コンテナー内のファイルのオーナーとグループ、 パーミッション を調整したい場合は追加の設定が必要となります。 詳しくは こちら をご確認ください。 x-mutagen : sync : # ... php-source-sync : # ... configurationBeta : permissions : # php コンテナー内ではファイルのオーナーとグループを apache にする defaultOwner : 'id:2000' defaultGroup : 'id:2000' 同期オプションは他にも いろいろあります ので、必要に応じてご確認ください。 同期セッション重複問題 Mutagen を導入してから、社内で特にファイル同期に関する問題が報告されなかったのですが、少しずつ、 MacBook の CPU 使用率が高い、見覚えのない差分が git status に出てる、などと相談が来るようになりました。 差分の問題はファイル同期と関係がありそうだと思ったので、その方向で調査を進めたら、相談者の MacBook で mutagen sync list が本来2つしかないはずのセッションを大量出力しました。 問題出力 -------------------------------------------------------------------------------- Name: rails-source-sync Identifier: sync_93JIPMqNq5WkYV20nV9Wq4XdvyBr3CXz3oonMfIkyYQ Labels: io.mutagen.compose.daemon.identifier: JH67_K5QB_5FG6_F4UH_OG45_7EKG_LBBY_7IPY_Z4ME_IKQY_HVSG_PPTU io.mutagen.compose.project.name: docker_buyma ... -------------------------------------------------------------------------------- Name: php-source-sync Identifier: sync_bzryoXJaLbxevdit2ODkZuGz2RChyN2C2W5wS8CdbdU Labels: io.mutagen.compose.daemon.identifier: JH67_K5QB_5FG6_F4UH_OG45_7EKG_LBBY_7IPY_Z4ME_IKQY_HVSG_PPTU io.mutagen.compose.project.name: docker_buyma ... -------------------------------------------------------------------------------- Name: php-source-sync Identifier: sync_FaC9uwjuhGziEeggNVbPI2EFgGUE1sxtKArzva4rSck Labels: io.mutagen.compose.daemon.identifier: T3PW_AONQ_MWDI_T5BO_Z6EH_6PQB_6CJZ_336T_M2KO_AXQH_ZSAQ_DQ7E io.mutagen.compose.project.name: docker_buyma ... -------------------------------------------------------------------------------- Name: rails-source-sync Identifier: sync_tPPnFvmEjlwKhLtkrudgukM3Qc7AHdTOc0QANYjwAmN Labels: io.mutagen.compose.daemon.identifier: T3PW_AONQ_MWDI_T5BO_Z6EH_6PQB_6CJZ_336T_M2KO_AXQH_ZSAQ_DQ7E io.mutagen.compose.project.name: docker_buyma ... -------------------------------------------------------------------------------- ... 出力を見てわかりますが、同期セッションが重複しています。 設定は一緒ですが、 daemon.identifier というものだけがそれぞれ違います。 daemon.identifier は Docker デーモンの id です。 デーモンはもちろん一つしかなくて、再起動しない限り id も変わらないはずです。 問題は Docker 開発環境を終了せず、 MacBook を再起動すると、発生していました。 原因としては再起動前に Mutagen のセッションを終了しなければ、再起動後に Docker 環境を立ち上げた時、古い同期セッションが残っていながらも、Docker デーモンの id が変わった影響で、同期セッションがまだ作成されてないと Mutagen が判断して、新しい同期セッションを作ってしまうということでした。 該当するイッシューはあります(問題を解消できないか検討中のようです)。 https://github.com/mutagen-io/mutagen/issues/243 対策としては Docker 環境起動後に mutagen sync list の出力を確認して、重複したセッション(現在の Docker デーモン id を使ってないセッション)があった場合、 mutagen sync terminate でそのセッションを終了するように スクリプト を作成しました。 MacBook 停止の際に Mutagen の同期セッションを必ず終了するようにするのも考えられる対策です。 M1 対応 新しい MacBook で ARM アーキテクチャ ーの M1 チップを使うことで macOS の業界で動かなくなってしまったものが多くあります。 なので、M1 対応をした時はもしかすると Mutagen が動かなくなってしまうと懸念しましたが、問題なく動きました。 インストールで調整は必要なく、同期も支障なく行われていますので、M1 で使う分には問題ないと思います。 docker-sync について Mutagen を使うようになる前に 0.5.1 の docker-sync をまず試しました。 docker-sync は ruby の gem と unison を生かした、Docker Desktop 専用のファイル同期ツールです。 仕組みも設定方法も Mutagen に似ていますが、Mutagen と違って macOS 側で動くプログラムが多く、rbenv/ ruby などのインストールが必要です。 Vagrant での環境構築ではそれらのインストール時に様々な問題が発生していたため、今回は rbenv/ ruby などのインストールは避けたかったです。 docker-sync で初回同期は問題なくて、パフォーマンスも Mutagen と同じぐらい改善されましたが、ファイル同期が不安定で、 macOS 側でファイルが変わっても、コンテナー内に反映されないことが多々ありました。 ファイル同期を強制するにも docker-sync のデーモンを再起動するしかなく、そうする度に CPU 使用率が跳ね上がって、 MacBook がドライヤーなみにうるさくなったりしていました。 docker-sync のイッシューを確認したら、開発者が問題を認識していても解決策が思いつかない状態のようでした。 なので、以上のことから docker-sync はあまりおすすめできません。 終わりに Docker Desktop for Mac のファイル同期のパフォーマンスの悪さで悩まされた期間が割と長かったのですが、Mutagen を導入することで完全に解消して、デメリットもほぼないので、同じ悩みを抱えられているなら、ぜひ導入をご検討ください。 Docker チームによる Docker Desktop のパフォーマンス改善に期待したいところですが、Mutagen レベルのパフォーマンスが実現されるまでどれくらい時間がかかるのかがわからない状態なので、そうなるまで サードパーティー に頼るしかないかと思います。 明日の記事の担当はエンジニアの橋本さんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
こんにちは、Corporate IT/Business ITを担当している足立です。 この記事は Enigmo Advent Calendar 2021 の 21日目の記事です。 代打として2回目の登場です。 2021年の前期はオフィスリニューアルPJの業務に追われていました。 コロナ禍に突入し出社とリモートワーク両方に最適化された環境を構築する事をミッションとして動きましたので、 今回はコーポレートIT目線で実施した事を書きたいと思います。 旧オフィスの課題 電源問題 リニューアルするなら旧オフィスで課題だった部分を改善したいと思いました。 総務的な話になりますが、特に電源については下記問題がありました。 電源 旧オフィス時にはOAタップが少なくタコ足配線かつ数珠繋ぎ状態で、あちらこちらにあり危険な状態 電子レンジを2台同時に利用した際にブレーカーが落ち iMac が強制シャットダウン状態になった事がありアンペア増強・単独系統への見直しが必要 → リニューアル後 座席毎に6個口OAタップが設置されました。 6個口のOAタップ 全てではありませんが、座席にモニターを常設しUSB-Cによる電源供給も可能にしました。 最大65 Wの電力を供給出来るので、PCの電源供給と映像信号が1つのケーブルで対応可能になりました。 DELL P2419HC オフィス レイアウト作成時に社内に設置する電子レンジは執務エリアと電源系統と別にして頂きました。 絶対にサーバールームの電源系統は他に影響しないように手配しました。 電話周り 電話システムはオンプレ型のPBX(レガシーPBX)で構成されていたため、頭を悩ませていました。 これも総務的な話ですが、気づいたら電話周りも担当していたので、どうにかしようと思いました。 課題 オフィスに設置する物理的な機器であるため、移設時には工事などが必要(PBX・配線) 運用保守は専門の業者に依頼をしている 今後、移転の場合は通信キャリア、専門業者との調整が必要になり時間がかかる 当然、電話は社内でしか使用出来ない PBXがサーバールームに設置してあり場所を取る(兎に角、邪魔でしょうがない) 旧 電話構成 → リニューアル後 クラウド PBXであるDialpadを導入しました。 Dialpad 導入した事によりPCや スマホ で会社の電話を利用出来るようになりました。 着信については会社が 保有 している電話番号(03番)にて着信するように、キャリア自動転送を利用してDialpadで発行された050の番号へ転送するようにしました。 ただし、発信はDialpadで発行した050の番号で発信する事になります。 シン・電話構成 最近、Dialpadでも0ABJ番号(03番)を別途、機器を設置せずにライセンス購入のみで利用出来るようになりましたので03番で発着信したい場合は、そちらを利用すると良いと思います。 導入後はWEB上で電話関連の設定が出来るようになりました。 転送機能 留守番電話 IVR設定(音声ガイダンス) 他にも色々と機能がありますが、特にIVRが利用出来るようになったので内容に応じて BUYMA カスタマーサポートやfondeskへ誘導出来るようになりました。 キャリア自動転送をする際に エニグモ の固定電話回線は ISDN だった為、キャリアに依頼してオペレーターの方に対応してもらうか電話機の操作で転送設定をする必要がありました。 ただ、従来の電話機は処分する必要もあり電話機の操作による設定は利用出来なくなり、かと言ってオペレーターの方に依頼する場合、変更まで日数がかかるので思い切って電話回線を 光収容 化しキャリアのWEBページ上で転送設定が出来るようにしました。 IVR構成一部 (この構成はモダンな情シス Kajinariさんの事例を参考とさせて頂きました。) IP固定電話 電話機は完全に無くすつもりで考えていましたが、バックオフィス部門から電話機が必要と言われたので 5台 POLYCOM VVX 350を設置しました。 VVX350 こいつが厄介だったのは、日本国内では購入は可能だが、サポートが受けられないのが難点でした。 お取引があるベンダーさん何社かに問い合わせしましたが、どこも販売までしか対応出来ないと言われ 覚悟を決めた上での購入でした。 不明な部分については基本、英語のマニュアルを翻訳して調べたり勘に頼ったりして設定しました。 それでも分からない部分についてはDialpadサポートの方が教えてくれました。 FAX コロナ禍直後は 複合機 から受信したFAXをメール送信→Zapier→Slackで通知、 Google ドライブに格納と言う構成でしたが、課題もありました。 複合機 の仕様上、用紙が切れるとFAXのメール送信が出来ない 送信が社内のみでしか利用出来ない PBXを撤去したいから電話線引きたくない 課題を無くす為、 KDDI のペーパーレスFAXを導入し クラウド 化を行いました。 当初は「FAX.PLUS」の導入も検討していましたが、FAXを利用する業務の大半が 経理 チームで主に銀行とのやり取りだったので、何かトラブルがあった際に日本企業の方が意思疎通が早いのかなと思い KDDI にしました。 WEB上でFAXの送受信が出来れば支障は無いので、特にこだわりはありませんでした。 シン・FAX システム構成 導入後、FAX受信はメール添付機能を利用し、旧構成と同じくZapierを利用してSlack上に受信通知、 Google ドライブへ保存するようにし FAX送信時はペーパーレスFAXのサイトにログインして使用するので自宅からでもFAX送信出来るようになりました。 受付システム・座席予約(ホテリング) 電話の話にも通ずるのですが、受付をどうするかも課題でした。 従来の受付ではエントランスに電話機を設置し総務担当が取次するスタイルで お客様がお見えになる際は「ご来社カード」と呼ばれる入退室管理の用紙に必要事項を記入して頂くフローでした。 ただ、このままだと下記課題が残ります。 受付の内線電話を撤去しなければオンプレPBXを撤去出来ない 外来者の入退室管理が紙による記入かつ集計作業が業務負担になっている 旧エントランス そこで「ACALL RECEPTION」を導入しました。 導入後、来客通知はSlackに変わり、お客様がお見えになった際は別途、設置したラベルプリンターから入館証が発行されるようになり 内線電話による受電対応が無くなり、入館履歴がシステム化され集計作業も不要になりました。 シン・エントランス ただ、一部部署にて宅配業者がお見えになった際にSlackの通知だと作業中に気づかないと言う相談があった為 宅配業者専用ボタンを作成しACALLとDialpadを連携、そこだけはIP固定電話にて着信する運用となりました。 座席予約システム(ホテリング) 新しいオフィスは面積が今までの半分になる為、 フリーアドレス を採用する事になりました。 リモートワークが主流になり、座席数がそこまで必要無くなりますが、その際に上司から座席予約システム導入を検討するよう指示がありました。 フリーアドレス フリーアドレス 2 個人的には必要なのか?と思いましたが、席のダブルブッキングや出社している人をひと目で可視化する為にも必要でした。 また、WEB MTG 用に個室ブースを8箇所設置する為、座席予約システム導入がマストになりました。 WEB MTG 用個室ブース 導入検討をしていた当時、ベストなソリューション選定に大変悩みました。 現在はコロナ禍を想定した座席予約システムが増えて来ましたが、当時としては・・・ 社内のコミュニケーション 円滑化を目的としたシステム(抽選式で座席を決めるシステム) IoTに特化した座席予約システム(各座席にセンサーを設置する為、費用も高額、電池交換も必要) 弊社としてはWEB上か スマホ で予約してはい、終わり!ぐらいなシンプルな機能を求めました。 ただ、上記の様に希望する要件と違ったシステムばかりでした。 悩んでいる矢先に受付システムで導入しようとしていたACALLに座席予約(ホテリング)機能がローンチされるとの情報が耳に入りました。 内容を見てみると希望する要件を満たすのではと感じ、受付システムと一緒に導入してみました。 座席マップ 一部 導入当初は予約した席にチェックインする作業に慣れない人も居ましたが、現在ではすっかり慣れました。 チェックインとは事前に予約した席に来たらACALLの スマホ アプリで座席に貼ってある QRコード を読み込む作業です。 予約した時刻から15分以内にチェックインしないと予約がキャンセルされます。 この機能が無いと予約したにも関わらず席を利用しない人が現れた際に他の人が利用する事が出来ないので重要な機能です。 特にWEB MTG 用の個室ブースは1時間単位で利用する事が多く、システムの利用頻度は高めです。通常予約システムを導入した会社によっては利用しなくなるケースがあると聞いたことがありますが、弊社は業務に欠かせないシステムとなりました。 入退出システム 同時進行でシステム導入や移設手配をしている矢先に今度は入退出システム導入の話が出てきました。 オフィスの一部分だけは入居しているビルが管轄している入退出システムを利用しているのですが、C工事に該当するエリアについては自社で入退出システムを入れる必要がありました。従来ではセキュリティ会社が販売しているオンプレのシステムを利用していましたが、このタイミングでリプレイスする事となりました。 (管理コンソールが IE でしかログイン出来ない・・・) 代表からは「顔認証」で入退出出来るようにして欲しいとオーダーがありました。 当初は クラウド タイプの入退出システムをベースに顔認証を付ける構成を考えていましたが、お見積を取ると費用がかなり高額になる事が分かりました。 顔認証を諦めてもらい、 スマホ アプリや FeliCa を利用したシステムを役員へ提案しました。 プレゼン時に代表からは「顔認証」を捨てきれない印象があり、再検討するよう指示があり 「色々、見た上で提案しているんだから、他にあるんだろうか・・・」と思っていたので思わず「じゃあ、おすすめのシステムあれば見てみます」と 逆質問に近いように返答したところ、代表の口から「Safieとか」と言われ早速、見てみると「Safie Entrance」なるものが。 クラウド 録画カメラのシステムとしては知っていたのですが、顔認証での入退出システムがあるのは知りませんでした。 早速、連絡を取り仕様や費用面を確認すると従来の顔認証システムに比べて圧倒的に安価に導入でき月々の費用もかなり安くなる事が分かり導入が決定。 若干、「こんなに良いものあるなら早く言ってよ」と思いましたが、代表のひと言が無ければ導入出来なかったと思います。 SafieEntrance エニグモ では5箇所に設置しました。事前にSafieEntranceで使用する iPad を準備しケーブルを最小限にする為に電源はPoEにて供給する事にしました。 通信についてはテストした結果、 Wi-Fi でも動作は問題ありませんでしたが有線LANで行っています。以前は鍵を使って物理的に入退出していた場所は全て顔認証になりエリア毎に入退出出来る従業員を制限しています。導入してみると顔さえあれば入出出来るのでが大変便利です。以前だと鍵にかけ忘れなどあり、それが無くなったので物理的なセキュリティレベル向上にも繋がりました。 個室ブース 以前に比べWEB MTG の需要が増えました。 エニグモ でも基本、 MTG はZoomで行う事が増え、その度に会議室が不足する事になりました。 その為、上司の案で個室ブースを設置しました。 個室ブース 設計当初、防音になると聞いていたので各ブースにはLANケーブルを敷設しました。昔、ITサポートの仕事をしていた時にあるお客様のご家庭に防音部屋があり、その部屋だけ Wi-Fi が受信出来なかった経験がありました。使用する材質によっては著しく Wi-Fi の強度が低下する恐れがあったのでお守り代わりに引くことをリク エス トしました。 設置後に通信が不安定になるとWEB MTG に取っては致命傷になる事を危惧していました。実際に設置後にZoomを利用した際にインターネットの速度が不安定ですと表示された事があり、やっぱり引いといて正解だと心から感じました。 現在はオフィスに必要不可欠な場所となり、出社が多い日は予約出来なくなる事もあります。 もし個室ブースを設置を検討している場合はブース内の空調と照明にはこだわった方が良いと思います。 密封されている空間なので空調を重要ですし、窓が無い場合は照明を明るく出来るようする事をオススメします。 最後に 社内ネットワークについて書こうと思いましたが基本、現状維持のままでの移設だったので特段書く事も無く割愛しました。 ただ、Wi-Fi6に対応させようかと思ったのですが、サポート切れか今後会社が移転する機会があればその時で良いかと思ったので何もしませんでした。 あとは社内のAV機器周りについても担当したのですが、それはまた別の機会にでも書きたいと思います。 (これで1本書ける内容なので。) 新しいオフィスになり半年以上経過し、日々働いていると本当に良いオフィスになったと実感します。 このタイミングで様々なものを導入させて頂いて、短期間で担当した事は自分に取って財産になりました。 そして、導入に関してGoサインを出してくれた上司、役員の皆様に感謝の気持ちでいっぱいです。 明日の記事の担当は サーバーサイドエンジニアのstevenさんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター
Next.js + Material UI v5 でフロントエンドアプリケーションを作成する なぜこの記事を書いたのか こんにちは。 エニグモ でサーバサイドエンジニアをしております、寺田( @mterada1228 )です。 この記事は Enigmo Advent Calendar 2021 の 20 日目の記事です。 業務では主に Ruby on Rails を使っているのですが、最近新しいチャレンジとして、フロントエンドの勉強をしています。 そこで、Next.js + Material UI(以降 MUI)を使った Web アプリケーションの開発にチャレンジすることにしました。 ただ、この2つは全く異なる開発形態の フレームワーク ですので、一緒に使うためにはちょっとしたセットアップが必要になります。 参考にするため色々と検索してみましたが、なかなか求めている検索結果が得られなかったことと、なぜこのようなセットアップが必要になるかまで詳しくまとめられているものがなかったので、今回自分で調べて一つの記事を書いてみることにしました。 なお、本記事では Next.js で MUI を使うためのセットアップに焦点を絞っているので、Next.js のプロジェクトを立ち上げる部分は説明しません。 その部分を詳しく知りたい方は、 公式チュートリアル を参考にしてもらえると良いかと思います。 Material UI を使うために必要なパッケージをインストールする MUI v5 のパッケージをインストールしていきます。 ひとまず、コアな機能をインストールすれば十分かと思いますので、 @mui/material というパッケージをインストールしていきます。他のパッケージについては、必要に応じてインストールして下さい。 また、MUI ではスタイリングのために内部で CSS in JS のためのライブラリを使用しています。 こちらは、 emotion もしくは styled-component のいずれかを入れる必要があります。 emotion を使う場合 // with npm $ npm install @mui/material @emotion/react @emotion/styled // with yarn $ yarn add @mui/material @emotion/react @emotion/styled styled-component を使う場合 // with npm npm install @mui/material @mui/styled-engine-sc styled-components // with yarn yarn add @mui/material @mui/styled-engine-sc styled-components next/link, @mui/material/Link の統合 Next.js からは、 next/link 、MUI からは @mui/material/Link という コンポーネント が提供されており、名前からお察しの通りどちらもカスタマイズされたリンク(a タグ)を生成します。 この2つには競合する部分もありますが、ある場面では next/link を、また他の場面では @mui/material/Link を用いた方が便利というケースがあります。(どういった場面でどちらを使うべきか、という理由はサンプルコードの後で説明します。) なので、状況に応じてどちらかの コンポーネント を返してくれるように、独自の Link コンポーネント を作成します。 src/Link.js ここで重要になってくるのは65行目から86行目の部分で、href に設定された URL が、 internal か external のどちらであるかを判定して、 return する コンポーネント を分けている、ということです。 URL が internal な場合は、Next.js が提供する、 next/link を返します。 これは Link がアプリケーション内のページ遷移であるときは、以下のような、Next.js が提供する各種機能を使用することができるからです。 Client-Side Navigation Code Splitting and Prefetching これらの説明は本記事の趣旨から逸れますので省きますが、詳しくは こちら をみて頂くと理解することができると思います。 URL が external な場合は、 next/link である理由はあまりないので、豊富なスタイリング用の Props が使用できる @mui/material/Link を使うようにします。 実際にリンクを作成する時は、ここで作成した、 src/Link をインポートする形になります。 pages/Index.js 独自 Theme を作成する こちらは作成せずとも、MUI の default Theme を利用できますが、多くの場合アプリケーション独自の Theme を設定していくことになると思いますので、本記事でも取り上げていきたいと思います。 今回は src 配下に Theme ファイルを配置していきます。例は簡単のために、 Typography の font-size だけを設定したものになります。 src/Theme.js 独自で定義した Theme は、 ThemeProvider を使って、アプリケーションに適用させることが可能です。 具体的な実装方法としては、 _app.js にて、 Component より外側の コンポーネント に ThemeProvider を設定してあげれば OK です。 pages/_app.js app.js, document.js の修正 最後に、Next.js で MUI を使用するために、 _app.js と _document.js の設定方法についてお話ししていきます。 はじめに、これから行う設定が何のためのものかというのを説明します。 MUI は元々、サーバサイド レンダリング されるものという制約のもと開発されています。 なので Next.js をはじめとした、クライアントサイドで レンダリング する可能性がある フレームワーク を使用した場合に不具合が発生してしまいます。 具体的にどのようなことが起きるかというと、いわゆる FOUC( Flash Of Unstyled Content)というもので、画面上に CSS の当たっていないページが一瞬表示されてしまいます。 これはクライアントサイド レンダリング 時に、HTML だけ表示されて、後から CSS が注入されるような流れになるため発生します。 なので、大まかに以下のような手順を踏むことでこの問題を回避していきます。 サーバサイド レンダリング 時に一度、 コンポーネント ツリーの レンダリング を行う そこから CSS を抜き出す( <style> を抜き出す) 抜き出した CSS をクライアントに渡してあげる この手順では以下のパッケージが必要となりますので、事前にインストールしていきます。 // with npm $ npm install @emotion/server @emotion/cache // with yarn $ yarn add @emotion/server @emotion/cache まず、 _document.js から説明していきます。 _document.js は Next.js においてあらゆる コンポーネント の初期化時に実行される コンポーネント ですが、その特徴として、サーバサード レンダリング 時に、サーバサイドで実行されるという特徴があります。 要するに前述した、1, 2 の手順をここで実行していくわけです。 サンプルコードは以下になります。 pages/_document.js 30行目からの処理に注目していきます。 getInitalProps はサーバサイド レンダリング 時に、ページにあらかじめサーバから取得した情報を埋め混むことができるメソッドです。 getInitialProps 内の60行目から66行目までの部分で、一度ページ コンポーネント の レンダリング を行なっていきます(前述した 1. の手順に相当) 以降の処理で、 レンダリング された コンポーネント から CSS ( <style> )を抜き出し、 emotion/cache を使ってキャッシュに保存します。(前述した 2. の手順に相当) 次にクライアントサイドの処理について見ていきます。 _app.js は _document.js 同様に、あらゆる コンポーネント の初期化時に実行される コンポーネント ですが、クライアントサイド レンダリング 時には、 _document.js は実行されず、 _app.js しか実行されないという特徴があります。 ここでは、 _app.js でサーバサイド レンダリング 時にキャッシュに保存した CSS を受け取るといった方法で、クライアントサイド レンダリング 時に発生する FOUC を回避します。以下がサンプルコードです。 11行目でサーバサイド レンダリング 時に保存した CSS を取得します。 このキャッシュに保存された CSS は17行目のように、 <Component> コンポーネント の外側を <CacheProvider> で囲ってあげることで、各ページ コンポーネント に渡されます。 ここでお話しした内容は、 MUI の公式ドキュメント でも詳しく説明されていますので、お時間ある方は合わせて見て頂けると理解が深まるかと思います。 おわりに 今回紹介したサンプルコードですが、 https://github.com/mui-org/material-ui/tree/master/examples/nextjs で MUI が公式に提供しているものです。今後変更がある可能性もありますので、サクッとコピペして使いたい方は、紹介したリンクから取得いていただくのが確実と思います。 明日の記事の担当は データテク ノロ ジー グループの堀部さんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
アバター