RAKSUL TechBlog

ラクスルグループのエンジニアが技術トピックを発信するブログです

ボトムアップから経営判断につなげて技術負債解消を実現する道筋

この記事は ラクスルの2022年アドベントカレンダー 4日目です。昨日は同じラクスル事業部のAMチームのタインさんの記事「Rails development environment troubleshooting on Mac with M1 chip - RAKSUL TechBlog」でした。 タインさんは日本在住で日本のチームで一緒に仕事をしていますが、普段は日本語で仕事をしています。ベトナムのメンバーも日本語上手な方が多くて日本語でのコミュニケーションも多いですね。ただ、英語を使ったり日本語で喋ったりと話の内容に応じて切り替えたりしています。

自己紹介と今日のテーマ

ラクスル事業部の印刷集客開発部長の加藤一平です。元祖加藤さんが居るので、私は「一平さん」と呼ばれることが多めです。

ラクスル事業は、ビジネス・オペレーション・プロダクト(プロダクトマネージャー、エンジニア)、という三つの役割の人たちで構成されています。 私はエンジニア組織でマネージャーのマネージャーという感じでお仕事しています。今は直接開発をしていなくて、ビジネスとの接続について動いていたり、事業の目標に沿った開発ができるような組織にする、みたいなところをミッションとして動いています。

今日のテーマはテクノロジ系じゃなくで事業・マネジメント系のお話です。 もしラクスルで開発するならどういう課題が転がっていて、それはどんな感じで解決可能な状態になってるのか?という話をしたいと思います。 思いっきりラクスルに興味持ってくれそうな方向け、面接で聞きたいことTOP3に入るテーマだと思いますw

エンジニアが仕事・会社を選ぶときの重要要素の一つとして「仕事の良し悪し」があると思っています。 ちょっと難しくても貢献度高めの課題があって、それが着手可能な状態でやっていいよ、って転がってたら喜んでその仕事を選ぶのではないでしょうか?ですよね? (もちろん、一緒に働く人が良いとか、作るモノが良いとかも大事。お金も大事!)

ラクスルの技術負債・課題について

ラクスルでは RPP(Raksul Platform Project)というのを 2018年頃からやっていましたが2021年に一つの節目を迎えています。( https://recruit.raksul.com/story/rpp03/ ) もともとモノレポだったラクスルのシステムでしたが、このプロジェクトによって現在はドメインごとにサービスが分割されて、程よい粒度で開発のしやすさが大きく上がっています。

という話を聞いていると、いまはモダンでいい感じの開発やってるよ、というポジティブなイメージを打ち出したい雰囲気を感じますね。 ところが、課題が大好きな身としては、これってそんなに響かなくて「まだ直すところが多くて」という方がワクワクしますw

実際、まだ技術負債がところどころに残っていまして、開発のスピードが上がらない要因になっているのが現状です。 新旧システムの両方に重複ロジックがあったり、旧システムの設計をそのまま移行先の新システムに持ってきているのでDB設計に無理があったりと、あまり色々書けないですがなかなか解決の困難なものから、時間があればどんどんやれる手元の軽いものまで課題と言えるものが転がっています。

また、昨年まで成長戦略・方針として「High Growth」を謳っていたこともあって、横へ拡大することにウェイトを置いて開発を進めていました。このため、運用面でも改善の余地の多い箇所がたくさんあるシステムになっている、というのも現状です。

(事例を書こうかと思ったのですが、文字数が倍増しそうなのでちょっとガマンします。他の方の記事から読み取れるかもしれませんね・・・?カジュアル面談とかで指名していただければ喜んで喋ります!)

技術負債に向き合う・基盤に投資する流れ

「技術負債」っていう単語とセットで気になるのが、経営陣はこの問題に賛同してくれるのか?ということではないでしょうか? ラクスル事業では、ここにとても強い関心とやりきることへの理解があります。 前述したRPPをプロジェクトとして経営承認して、数年がかりでやってきたことでもわかるようにテクノロジーをとても大事に扱っています。

ビジネスの実行力がとても強いことがラクスルの成長・事業としての強みを牽引しています。 しかし、ラクスル事業CEOやビジネス統括から「しっかりやりきる」というミッションを提示しており、決してビジネスサイドがパワーを持ってゴリゴリと推し進めるだけではない、というのがラクスル事業の強みの理由だと言えるでしょう。 「仕組みを変えれば、世界はもっと良くなる」というビジョンをそのまま体現しているのがラクスルであり、テクノロジーの力とそれを実現するプロダクトメンバーに信頼を置いて任せています。これがラクスルの変わらないスタイルなのだな、と実感しています。

大体みなさん同意してくれるのですが、エンジニアで「課題が好きじゃない人」って居ないですよね?

肝心なのはその先、その課題を解決する余地があるのか?解決できる環境があるのか?ってことだと思います。これは明確に Yes で、まだまだ「現状すごくムーブメントがあって・・・」というところまで行ってないですが、半年後にはこれがじわじわと流れになっているはず。

私が入社してから、特に体制を整備するところに大きくエネルギーを費やしてきました。

事業を推進するのに足る体制を作ることに加えて、技術負債や開発を阻害する課題を解消していく。このために、必要な対処ができるということも並行してやっていけるように、ということを明確に意図しています。もともとの流れもありますが、このウェーブにしっかり乗ることで課題解消環境が整っていっていきます。

ラクスルのエンジニアはどうあるべきか?

このウェーブに乗りそこねないためにやるべきことがあります。

CTOや私がやってきていることは、「任せてくれたらしっかりやりきるよ・やりきらせますよ」っていうアピールなのですが、これはエンジニアメンバーの「しっかりやりきる」ということへの期待を前提にしています。エンジニアの仕事は投資(開発)に対してのリターン(効果・成果)までの期間がちょっと長めなので、なかなか説明や投資判断の難しいところですよね。

「約束」することの難しさはありつつ、信用・信頼をもとに仕事を任されていくので、説明責任はもちろんのこと、しっかりやりきること/それを推進することを明確に求められます。

(ちょっと “自分の考え” も含みますが)説明責任には「計画の提示」を含み、状況の変化を適切なタイミングで提示していくことなどがとても重要になってきます。

説明は任せて、と私から全方位に言っていきたいので、「説明の素材」と「やりきりはまかせて」、というのはみんなにお願いしたいと思っています。

ごくごく基本的な仕事のスタンスですが、有言実行で「提示しておいたもの(計画)を実行する」ということがとても重要です。

これはエンジニアリングなら「設計したものを実装する」(設計しながら実装するじゃん、ってのは一定あると思いますが)というのと同じで、設計したけど実装してみたらちょっと思ったんと違った、といって設計見直すのと近いものだと思っています。計画を立てたけど実行してる途中でちょっと変わった、っていうのを調整していくのと同じですよね。

エンジニアリングもマネジメントも、ほかのすべての仕事についても「考えたものを実行する」というのは同様で、これが事前に考えたものに近いかたちで進められるというのは、「再現性のあること」がやれる証左になると言えます。

まとめ

事前に考えを提示してから実行していく、これを繰り返していくことで「再現性がある」と見られることに繋がり、任せていこう、って思われていきます。

ラクスルのエンジニアは、開発力と事業理解が強いメンバーが非常に多いため、こういった面での「再現性」を強めていくということを期待しています。

また、「課題大好き・任せてくれたらやりきりますよ」という方と一緒に仕事をしたいですね!

Rails development environment troubleshooting on Mac with M1 chip

Hi everyone! My name is Thanh, I’m working in the Area-Marketing team at RAKSUL.

Raksul’s services are built on several technologies, including Ruby on Rails. Setting up Ruby and Gems is the first step to working with the Rails environment, but it’s not a frequent task. So I have lost so many hours fighting compilers to install Ruby and related gems on macOS Monterey M1. In this post, I will share my experience to help you avoid and fix the issues.

Contents

  1. Detecting macOS CPU architecture
  2. Homebrew
  3. Ruby issue
  4. Gem issues
  5. Reference
  6. Recap

Detecting macOS CPU architecture

On macOS, two architectures are supported

  • x86_64 is the architecture of Intel's 64-bit CPUs, aka x64. It was shipped between 2005 and 2021.
  • arm64 is the architecture used by newer Macs built on Apple Silicon, shipped in late 2020.

To know current architecture is the first step before starting to install compatible libraries/tools.

<arm64>
% uname -m
arm64
<x86_64>
% uname -m
x86_64

Homebrew

Issue: The current project has an installation script to collect all required tools in one place. Meanwhile, setting up the development environment got an issue with both versions of homebrew installed (arm64 and x86).

How to solve it: To reinstall Homebrew using the official method, it will correct the referenced prefix. The below info help you confirm Homebrew on your PC.

Installation

% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Homebrew location

<arm64>
% which brew
/opt/homebrew/bin/brew
<x86_64>
% which brew
/usr/local/bin/brew

What is the reason that Homebrew has changed the prefix instead of /usr/local for Macs Apple Silicon?

  • The path /usr/local is also used by other tools, not only Homebrew. This can make conflict
  • Homebrew tools might be used by default without configuration to avoid the above issues making an alternate prefix would be easier.

You can find the detail of the Homebrew discussion at here

Tips:

You will do bundle install later on, knowing the Homebrew prefix and location of tools help you run the correct reference libraries.

Ex: brew info openssl@1.1

The command show detail of the installed library that helps to know the configuration of OpenSSL, such as LDFLAGS, CPPFLAGS, and PKG_CONFIG_PATH etc.,

% brew info openssl@1.1
==> openssl@1.1: stable 1.1.1q (bottled) [keg-only]
Cryptography and SSL/TLS Toolkit
https://openssl.org/
/opt/homebrew/Cellar/openssl@1.1/1.1.1q (8,097 files, 18MB)
  Poured from bottle on 2022-10-04 at 07:33:33
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/openssl@1.1.rb
License: OpenSSL
==> Dependencies
Required: ca-certificates ✔
==> Caveats
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /opt/homebrew/etc/openssl@1.1/certs

and run
  /opt/homebrew/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /opt/homebrew,
because macOS provides LibreSSL.

If you need to have openssl@1.1 first in your PATH, run:
  echo 'export PATH="/opt/homebrew/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/opt/homebrew/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/opt/homebrew/opt/openssl@1.1/include"

For pkg-config to find openssl@1.1 you may need to set:
  export PKG_CONFIG_PATH="/opt/homebrew/opt/openssl@1.1/lib/pkgconfig"

==> Analytics
install: 1,158,119 (30 days), 2,567,700 (90 days), 11,370,947 (365 days)
install-on-request: 52,608 (30 days), 103,510 (90 days), 418,568 (365 days)
build-error: 1,533 (30 days)

Ruby issue

I got the issue when installing ruby 2.6.6

Issue:

% rbenv install 2.6.6
To follow progress, use 'tail -f /var/folders/rd/r2g0jzln74s6t3636gb6m8dh0000gq/T/ruby-build.20221110172716.6727.log' or pass --verbose
Downloading ruby-2.6.6.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.6.tar.bz2
Installing ruby-2.6.6...
ruby-build: using readline from homebrew

BUILD FAILED (macOS 12.6.1 using ruby-build 20220930)

Inspect or clean up the working tree at /var/folders/rd/r2g0jzln74s6t3636gb6m8dh0000gq/T/ruby-build.20221110172716.6727.iLaZEA
Results logged to /var/folders/rd/r2g0jzln74s6t3636gb6m8dh0000gq/T/ruby-build.20221110172716.6727.log

How to solve it:

After confirming the logs, disable the warning by set RUBY_CFLAGS="-w" to fix the issue. Also, Github issue solved a similar error with set environment variables.

% RUBY_CFLAGS="-w" rbenv install 2.6.6

To follow progress, use 'tail -f /var/folders/rd/r2g0jzln74s6t3636gb6m8dh0000gq/T/ruby-build.20221110174033.21130.log' or pass --verbose
Downloading ruby-2.6.6.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.6.tar.bz2
Installing ruby-2.6.6...
ruby-build: using readline from homebrew
Installed ruby-2.6.6 to /Users/XYZ/.rbenv/versions/2.6.6

Tips:

Tip 1: correct OpenSSL installation

When you install openssl via brew, let set a specific version for it, otherwise openssl@3 will be installed on Monterey

https://formulae.brew.sh/formula/openssl@1.1

brew install openssl@1.1

https://formulae.brew.sh/formula/openssl@3

brew install openssl@3

Tip 2: Ruby version required different OpenSSL version

For Ruby versions 2.x ~ 3.0

brew install openssl@1.1 readline libyaml gmp
export RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(brew --prefix openssl@1.1)"

For Ruby versions 3.1 ~ above

brew install openssl@3 readline libyaml gmp
export RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(brew --prefix openssl@3)"

Gem issues

Issue 1:

Using gem version still does not support arm64. Currently, using twirp (1.7.2)

/Users/XYZ/R/github/r/vendor/bundle/ruby/2.7.0/gems/zeitwerk-2.5.4/lib/zeitwerk/kernel.rb:35:in 
`require': cannot load such file -- google/protobuf_c (LoadError)

How to solve it:

Check the gem-released version and upgrade it.

gem 'twirp', '~> 1.9.0'

Issue 2:

OpenSSL didn't match reference environment variables

ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [rubyeventmachine.bundle] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/XYZ/.rbenv/versions/2.7.6/lib/ruby/gems/2.7.0/gems/eventmachine-1.2.7 for inspection.
Results logged to /Users/XYZ/.rbenv/versions/2.7.6/lib/ruby/gems/2.7.0/extensions/arm64-darwin-21/2.7.0/eventmachine-1.2.7/gem_make.out                

How to solve it:

Follow steps to correct environment variables for OpenSSL

  1. Confirm the installation of openssl brew info openssl@1.1
  2. Export environment variables
% export PATH="/opt/homebrew/opt/openssl@1.1/bin:$PATH"
// For compilers to find openssl@1.1
% export LDFLAGS="-L/opt/homebrew/opt/openssl@1.1/lib"
% export CPPFLAGS="-I/opt/homebrew/opt/openssl@1.1/include"
% export PKG_CONFIG_PATH="/opt/homebrew/opt/openssl@1.1/lib/pkgconfig"

Or you can add it to the bundle local config

% bundle config --local build.eventmachine --with-openssl-dir=$(brew --prefix openssl@1.1)

3.Runbundle install

Reference

To learn more, refer to the following resources

  • Some influential environment variables:
  CC          C compiler command
  CFLAGS      C or C++ compiler flags
  LDFLAGS     linker flags, e.g. -L<lib dir> if you have libraries in a
              nonstandard directory <lib dir>
  LIBS        libraries to pass to the linker, e.g. -l<library>
  CPPFLAGS    (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
              you have headers in a nonstandard directory <include dir>
  CXX         C++ compiler command
  CXXFLAGS    C++ compiler flags
  CPP         C preprocessor

Recap

When you are facing installation issues check the environment health, by following the commands

  • uname -m: to know your architecture
  • brew doctor: confirm the output of brew config
  • check ruby installation
% ruby -e 'require "rbconfig"; pp RbConfig::CONFIG' | grep "host"
 "host_os"=>"darwin21",
 "host_vendor"=>"apple",
 "host_cpu"=>"arm64",
 "host"=>"arm64-apple-darwin21",
 "host_alias"=>"",
  • check bundle platform
% bundle platform
Your platform is: arm64-darwin21

Should collect the current environment first before executing the commands that you search from google or GitHub issues. Otherwise apply the wrong parameter or environment variables the error is always the same and it seems to have no effect.

Dagster Cloudの導入チュートリアル

Data Engineeringチームの岩崎です。 今年の9月よりRaksulに業務委託として参画させていただいております。普段は様々な企業様にてETLパイプラインの構築やシステム設計などでお仕事させて頂いております。もしよろしければ私の活動についてもご覧ください。

今回は、Dagster Cloudの検証で得た知見を元にDagster Cloudの導入からそのコンセプトの一部を紹介させていただければと思います。

はじめに:Dagsterとは🐙

Dagsterとはデータパイプラインツールの一つです。 Raksulでは取り扱う膨大なデータの処理や分析に日々改善を続けており、Dagsterもその一環としてデータ分析処理改善のため導入検証を進めています。 今回は、Dagsterのマネージドサービスである Dagster Cloudを中心にその設定や導入と、コンセプトについて簡単に紹介します。

データパイプラインにはすでに有名どころとしてAirflowがございますが、DagsterはAirflowを機能的に乗り越えるべく、DAGの視認性・可読性の問題、テスト容易性、複雑な依存関係の解消など、現代的な分析業務に即した構成を目指し開発されました。 国内ではまだ導入事例が少ないのですが、海外ではすでにモダンデータスタックの一つとして確固たる地位を得ているため、国内での知名度を上げるためここで取り上げさせていただきました。

Dagsterのデータパイプライン例 ※引用元:Dagster Docs

Dagster Cloudの概要と導入チュートリアル🔧

Dagster CloudはDagsterのマネージドシステムです。サービスがスタートしたのは2022/8/9とまだ日が浅いですが、Dagster 単体としてはすでに三年以上の開発実績があり、海外では導入実績例も多数・開発コミュニティでの議論も活発で短い期間でリリースが繰り返されています。 DagsterはDockerやk8s、AWSのECSなどといった形でも動作させることができますが、ここでは、以降ECSで動作させることを想定して説明していきます。

Dagster Cloudのシステム概要

チュートリアルの前に、Dagsterのシステム概要について簡単に触れておきたいと思います。

Customer EnvironmentとDagster Cloudの関係 ※引用元:Dagster Docs
左側Customer EnvironmentではAgentがDagsterのシステムを統括しており、一方でUser Code(Job)はデータパイプライン処理を指していて、外部のリソースに対してアクセスやデータ処理を行います。Dagster Agent、User Code(Job)はそれぞれDocker Imageの中に必要なコードとモジュールを導入した上で提供する形となっております。

右側Dagster CloudではAgent APIがCustomer Environment側のAgentとの通信を担っています。運用に必要なWebUIやオーケストレーション、実行スケジューリングやジョブ実行履歴などのメタデータの構成・蓄積はマネージドで提供されており、構成管理を意識する必要はございません。

導入チュートリアル

Dagster Cloudで最初のパイプライン実行までの基本的な流れについて、設定の順に従って紹介します。

Create a Dagster Cloud account and organization

Dagster Cloudへの登録は、googleアカウント・githubアカウントなどで可能です。登録すると、まずOrganization名を決める必要があり、以降も登場するDeploymentに対応する名称となります。

最初にOrganization名を決める

Select a deployment type

Dagster Cloud構成の提供形態はHybrid DeploymentServerless Deploymentの2種類があります。

Hybrid Deploymentとは、Dagsterの実行環境は自前で準備した環境を用い、構成情報の管理はDagster Cloudに任せる提供形態のことです。自環境として利用できるのは、現在k8s、ECS、Docker、localの選択肢がございます。 一方で、Serverless DeploymentとはDagsterの構成環境を全てDagster Cloud内に構築する方式で、ユーザが準備するのはデータを保全するストレージのみになります。

ここではHybrid DeploymentをAWS ECS環境で構築した場合を中心に紹介していきたいと思います。

補足:Dagster Cloudの利用コストについて

なお、Dagster Cloudの利用コストについてはこちらにも記載されていますが、以下の図の通りになります(※登録開始から30日以内は料金がかかりません)。 パイプライン処理を実行している時間(per minute)毎に$0.03(Hybrid)または$0.04(Serverless)かかる従量課金制となっており、環境がスケールするにつれてコストが割安になっていく方式です。

エンタープライズプランについては公表されておりませんが、一般的なSaasサービスのエンタープライズ価格相当と考えて問題ありません。エンタープライズプランだとDeployment数を増やせたり、認証認可に任意のSAML方式を利用したりなどできることの制限が緩和されます。

Standardプランを選んだ場合

Deploy Your Code

自環境において、まずはDagster AgentをECSとして立ち上げます。ちなみにDagster Agentは公式のDocker Imageも使用可能です。 また、ここから先はAWSで環境構築する場合Cloud Formationを用いて自動的に構築することも可能です。 Cloud Formationの手順は公式にも掲載されているためそちらをご覧いただくとして、ここではCloud Formationの設定内容を簡単に説明していきたいと思います。

ネットワーク構成

Cloud Formationを使用してネットワーク構成を組む場合、自動的にVPCの構築、Route53、Service Discoveryなどが設定されます。

Dagster Agent(ECS Service)

Dagsterではパイプライン実行環境はDockerで提供します。Dagster AgentとDagster Task(パイプライン実行環境)はECS Serviceとして常時起動した状態となります。

Dagster Cloud内で通信に必要なAgentのTokenが提供されており、そのTokenをDagsterのDocker Agentに環境変数として適用すると、自環境のDagster AgentとDagster Cloudの通信が始まります。

Agent APIのToken確認画面

上記に挙げた構成は、全てDagster Agentが持つdagster.yaml内にも記載されています。

補足 : dagster.yamlの形式

※ branch_deployments: trueにしなければBranch Deploymentできないため注意してください。

instance_class:
  module: dagster_cloud
  class: DagsterCloudAgentInstance

dagster_cloud_api:
  url: https://<ORGANIZATION名>.agent.dagster.cloud
  agent_token: agent:<ORGANIZATION名>:xxxxxxxxxxxxxxxxxxxxxx
  deployment: <ORGANIZATION名>
  branch_deployments: true

user_code_launcher:
  module: dagster_cloud.workspace.ecs
  class: EcsUserCodeLauncher
  config:
    cluster: <ECS Cluster名>
    subnets: [<subnet ID>,.....]
    service_discovery_namespace_id: <Namespace名>
    execution_role_arn: arn:aws:iam::<Account ID>:role/<Task実行ロール名>
    task_role_arn: <Taskロール名>
    log_group: /ecs/<Log Stream名>

Launch a run

環境が構築されるとパイプラインのジョブを実行する準備が整います。 Workspaceから任意のパイプラインを選択し、JobのページでLaunchpad タブをクリックします。 遷移した画面で右下のLaunch Runをクリックしてジョブを実行します。

JobページのLaunch Pad

Create a Branch Deployment

後ほど詳細に説明しますが、Deploymentの種類にはProduction DeploymentとBranch Deploymentがあり、ここまでの作業はProduction Deploymentでの説明でした。しかし、一連の作業を開発環境で検証したい場合も当然あると思います。そのためにBranch Deploymentが用意されています。

このステップは実際の設定作業でもスキップ可能であり、後でいつでもセットアップが可能です。

Dagsterパイプラインのコンセプト♻

DagsterのパイプラインはPythonコードで記述します。パイプラインの基本的な構成要素はそれに対応したUIと機能が提供されており、以下の図はパイプラインの持つ各機能と、それら機能をディレクトリ単位で分けた構成の一例です。

ここでは、特に中心的な機能 Repository・Assets・Jobs・Graphs・Opsを簡単に紹介します。

なお、少し古めの記事ですと似たような機能が違う表現で紹介されていたりしますが、現在こちらで紹介した機能が最新版となっています。 それぞれに対応する機能を実装する際にはデコレータを用います。また、他にもいくつか紹介し切れない機能もございますが、詳細はドキュメントをご確認ください。

パイプラインの基本機能とディレクトリ構成例

Repository

Repositoryとは、一つのパイプラインを構成する要素を定義する単位のことです。これから紹介するAssets、Jobs、Graphsなどの構成をRepositoryの中に指定することで一つのパイプラインとして表現します。UI上からはWorkspaceなどからパイプラインとそれぞれの構成要素を確認できます。

RepositoryはWorkspaceで確認できる

assets

アセットとは、Dagsterの外部にあるデータ資産を指します。Dagster上では一つのデータの塊の単位をアセットとして管理できます。

例えばSQLクエリ発行による結果をアセットとして保持したり、データ内容を確認したり、データ処理時間をグラフで確認したり等、視覚的・一覧的な構成管理に役立ちます。アセットには上に挙げたようなDBのテーブルや、機械学習用のデータモデル、Slackのチャンネルなど様々な情報が対象になります。

ちなみに、Dagsterでアセットだけ最新の情報を取得したい場合があると思います。その時にはMaterializeという操作でリフレッシュ可能です。

アセットから確認できる情報※引用元:Dagster Docs

op

OpはDagsterのパイプラインの処理の一つの単位です。以前まではsolidと呼ばれる単位で記述されていましたがバージョン0.12以降から変わりました。Dagster パイプラインのUIでは一つの処理のユニットとして表現され、定義内容や設定情報、処理時間、入力、出力情報など様々な情報を確認することができます。

opをクリックすると情報を確認できる ※引用元:Dagster Docs

job

ジョブはパイプラインのジョブの実行起点です。opで定義した処理をjobとしてあらためて定義すると、パイプラインからジョブ実行できるようになります。 実行の際にはLaunch a runの項目で説明したようにLaunch Padから指定します。 また、今回は紹介しませんがユニットテストやlocal環境でのテストにも対応しています。

ジョブ実行の様子

sensor

センサーとは、処理による状態変化を監視するトリガー設定のことです。 ジョブ実行完了が非同期であったり、処理の返却値以外に次の処理に遷移する実行契機を設けたい場合など、任意で指定した状態変化に基づいた処理を実行できます。例えば以下のような使い方をします。

・s3バケットにファイルがputされたら実行開始する
・他のジョブが特定のアセットをマテリアライズするたびにジョブ実行する
・外部システムがダウンしたときにジョブ実行する

センサー情報の一例 ※引用元:Dagster Docs

graph

グラフは各opの処理を結合や分岐を定義します。処理の分岐結合や、アセットの分岐結合に用います。

Dagster Cloudのパイプライン実行環境 🔀

Dagster Cloudが提供する環境におけるDeployment、Location、Workspaceという関係性は少し複雑で、それぞれ以下のような関係性となっております。

Deployment・Location・Workspaceの関係

図における太枠、(n)と表している箇所は複数個作成可能であることを示しています。各リソースと対応する環境は以下の関係となっています。

Repository : Production Deployment = 1 : 1
Pull Request : Branch Deployment = 1 : 1
Deployment : Locations = 1 : n
Locations : Workspace = 1 : n

Deployment

Githubリポジトリ上のコードをDocker ImageにまとめてDagster Cloud上とAWS環境にデプロイすることを指します。 Dagster Cloudでは大きくProduction DeploymentBranch Deploymentが存在し、Production DeploymentはGithub Repositoryと1 : 1で対応しております。Production Deploymentできる数を増やしたい場合は、Enterprise Planが必要になります。

Github Actionsでproductionまたはbranch deployが提供されており、Dagster CloudではGithub Actionsでの提供方式が一般的です。Github Actionsを使わない場合、デプロイはCLI dagster-cloudコマンドラインから実行します。

production・branchデプロイ※引用元:Dagster Docs

Github Actionsで用意されているアクションとしてはmaster/mainブランチに対するpushをトリガーにdeployする方式と、develop branchのPull Requestに対する変更操作をトリガーにdeployする方式がございます。

補足 : Github Actionsの設定

2022/12/02現在、githubで記載されている設定情報のSetup Secretsは少し誤っていて、ORGANIZATION_IDの設定にはORGANIZATION IDがpied-piperだったらhttps://dagster.cloud/pied-piperもしくは https://pied-piper.dagster.cloudという記載となっていますが、実際には以下の設定となっております。

名前 設定内容
DAGSTER_CLOUD_URL https://dagster.cloud/<ORGANIZATION ID> の形で設定
ORGANIZATION_ID https://<ORGANIZATION ID>.dagster.cloud の形で設定

また、提供されている方式ではクレデンシャル情報の設定を示していたりしますが、最近ではOpenID Connectによるクレデンシャル不要な方式も提供されているのでそちらの方がより安全にアクセス可能と考えます。

また、Branch Deployment環境の話ですが、以下の機能は制限され使用できません。

・Schedule
・Sensors
・Backfills(部分的にアセットを読み込ませてデータを補完する機能)

Locations

Locationsとは、一つのDocker Imageで構成される環境を指します。Dagsterの実行環境はDockerで構成されますが、LocationsをDeploymentの中に複数作成することが可能です。設定の仕方は以下を参考にしてください。また、dagster_cloud.yaml内で環境変数を設定することも可能です。

Locationの例

補足 : dagster_cloud.yamlの例

location_name: <任意のLocation名>
image: <ACCOUNT ID>.dkr.ecr.ap-northeast-1.amazonaws.com/<リポジトリ名>
code_source:
  package_name: <パッケージのディレクトリ>
git:
  commit_hash: <HASH>
  url: <Github_URL>

なお、今現在Dagster CloudではLocations毎で別々のTask Roleを設定することができず、Dagster Agentが持つTask定義ファイル一つで全ての同一のTask定義を用いていますが、私がサポートにFeature Requestを投げて近日中にLocationごとのTask定義を適用できるようにご対応いただけるとのことでしたので今しばらくお待ちください。

Workspace

Workspaceとは一つのパイプラインの構成単位を指します。Dagster CloudのLocationsの中にWorkspaceを複数作ることができます。 Workspaceを読み込む際には、workspace.yamlへの記述が必要になります。

補足 : workspace.yamlの例

Workspaceは複数指定することが可能で、指定した数のパイプラインを作成することができます。Workspaceの指定の仕方として、他にもコード単位でも指定可能です。

load_from:
  - python_package: <プロジェクト名>

Workspaceの中にパイプライン構成がある

おわりに:Dagsterのネクストステップ🔜

いかがでしたでしょうか。どんなツールでも導入コストはございますが、Dagsterは一度理解すれば直観的にその内容を理解しやすく、概念も洗練されたサービスとなっております。今後、より詳細に概念を説明する記事も執筆予定です。本記事が理解のためのコストを下げるための一助となれば幸いです。

Sidekiq queues & job priorities management

💡 Background processing is a critical part of any web backend, and Sidekiq is a very popular background job processing system for Ruby for many reasons, it can also be integrated as the backend to theActiveJob interface as well. Many projects inside Raksul utilize Sidekiq, but although Sidekiq provides several different queue processing strategies out of the box (random, strictly ordered, and weighted job priorities) , sometimes you still encounter different problems with queues & job priorities management that those strategies cannot solve easily. This small article will try to tackle some examples of such problems, together with some principles that I've learned during my work, about how to prioritizing background jobs appropriately.

Basic queue processing strategies

  • Strictly ordered job priorities: declare queues in the order you want them to strictly run, without weight options.
# config/sidekiq.yml
...
:queues:
  - critical
  - default
  - low
  • Weighted job priorities: the weight you put on each queue will determine how frequently it'll be scanned relatively compared to other queues.
# config/sidekiq.yml
...
:queues:
  - [critical, 5]
  - [default, 2]
  - [low, 1]
  • Random job priorities: Just queues that are all set to the same weights. Not really practical.
  • sidekiq-priority is also an interesting gem that helps you to prioritize some specific jobs with specific arguments. It can prioritize certain jobs to be processed ahead of other jobs (even of the same job class) that are queuing in the same queue. It's pretty handy in some niche scenarios.

Principles & tips about prioritizing jobs

  • Jobs with different criticality levels must be separated into different queues. Don't be afraid and use as many queues as you want, as long as all the jobs in the same queues have similar priority & criticality levels. Mailing jobs can be put in the same queue as Elasticsearch indexing jobs, as well as ActionCable broadcasting jobs, for example. Don't stop at default and critical_as_f*ck, you may have slightly_lower_priority, lowest_priority, _no_one_cares, midnight_schedules, etc as well. Separate queues with clear intentions will help you a lot later on.
  • Balancing jobs based on criticality is not enough, as obviously not all jobs are born equal. Some may flood the workers in a short period, some may consume a big chunk of execution time, and some only run at a specific hour of the day. That's why there are few other metrics that you should consider when assigning jobs to queues: blocking potential, average execution time, frequency of arrival, and last but not least, latency tolerance.
    • Blocking potential is the potential of blocking entirely all other jobs in the same queue with them from being processed, causing noticeable latency.
    • Jobs that have high average execution time (in the order of minutes or hours) combined with high arriving frequency will exhibit high blocking potential as well.
    • Latency tolerance is a metric in which you evaluate the impact of latency on the nature of the job itself. For example, indexing jobs that affect search results usually have high latency tolerance, since most of the time, users don't know exactly what's in the search result they're looking for.
  • Sometimes high latency tolerance translates to low criticality, and vice versa, but that's not always the case. Some jobs may have high criticality, but can still tolerate some latency (Elasticsearch indexing jobs, for example). They can be put together with other jobs that have high criticality but also a high blocking potential (In other words, having potential to block entirely the queue for a brief period, causing latency for other jobs in the same queue with them).

Advanced queuing with multiple processes (swarm as in Sidekiq Enterprise)

  • What if, two different classes of job have similar criticality, but cannot tolerate each other since one has high blocking potential and the other has low latency tolerance? And you want them both to be processed in the same timeframe without stomping each other's feet?
  • What if, you want your high priority queues to be processed with the highest speed regardless of any events in other queues? In normal weighted queues, unfortunate timing can cause high priority queues to be contaminated with, or even blocked by a mass number of low priority jobs that simply arrived at a bad time.
  • What if, you utilize a fancy compute node with lots of CPU cores to hopefully speed up your job processing speed? But then only to find out later that no matter how many threads your sidekiq process spawn, they all run on the same core? (all Sidekiq threads run concurrently instead of in parallel, due to global interpreter lock in CRuby. They will eventually hit the wall of diminishing return as increasing number of threads saturates the allocated core).

→ Here comes multi-process sidekiq to the rescue (or better, pay $1900/year for Sidekiq Enterprise's swarm for minimum config of 10x10 threads):

# config/sidekiq_urgent.yml
...
:queues:
  - critical
  - default
# config/sidekiq_relax.yml
...
:queues:
  - default
  - low

Balancing queues between processes is needed to pursuit meaningful purposes: ensure maximum throughput for VIP queues, offer fault tolerance to some extends when one of your processes encounter problems, and better utilize computing resources (since multiple processes can span to use multiple cores and have address spaces of their own).

Similar principles as when balancing jobs between queues can be applied to queues between processes too. And in the broader picture, balancing processes between compute nodes / containers, or balancing containers across availability zones. They will allow you to provide utmost availability & stability to your sidekiq fleet.

Some Interesting reads

https://sensortower.com/blog/how-we-scaled-to-thousands-of-sidekiq-workers

https://about.gitlab.com/blog/2020/06/24/scaling-our-use-of-sidekiq/

https://medium.com/@rajagopals/scaling-sidekiq-in-production-ca4d3d0002db

Goのバージョンをサクッとアップデートできた話

初めまして! ラクスル株式会社 SCM(Supply Chain Management)チームの Jang Junsu です。今年でラクスルに新卒入社して4年目に入りましたが、テックブロックの発信は始めてなのでドキドキしています。笑

今回は、SCMプロダクトで使用中のGoのバージョンをサクッとアップデートできた話をしたいと思います!

始める前に

本格的な話をする前に、Goのバージョンサポート期限とバージョンアップの背景を少しお話しします。

Goのリリースはとても早く、6ヶ月単位でメジャーリリースが行われます。ただ、「最新の2バージョン」だけがサポート対象となるため、大体1年ぐらいでサポートが切れてしまいます。 ラクスルではシステムをセキュリティを含め安全に稼働させるため、様々なプロダクトの環境や言語バージョンの管理を行っており、常に最新バーションの検証及び追従を奨励しています。

このような文化下で、我々SCMチームは「ラクスルの成長を支える発注基盤システム構築」をミッションにお客様へ高品質かつ安定的な体験を届けるため、言語バージョンやインフラ環境などプロダクトの健康状態に関わる所を常に意識しています。

バージョンアップの準備

このような懸念点が存在し、問題が発生した場合の原因究明が非常に難しいため、使用中の言語のメジャーバージョンアップは慎重に考慮する必要があります。

  1. アプリケーションコードのDeprecated
  2. 使用中のフレームワーク・ライブラリとの互換性
  3. 新しいコードスタイル
  4. 言語内部仕様変更によるパフォーマンス変化

実際に、以前他言語のバージョンアップで内部実装の変更によるパフォーマンス悪化の前例があり、今回は4番に特に気をつけていました。

リリースノートを読む

当たり前の話ですが、まず担当者がリリースノートを読み、内容と懸念点を整理します。不明な所がある場合はチームに相談して全員の合意を取ることが大事です。リリースノートのダブルチェックができればより安心です。

テストパターンの洗い出し

テストは単純なコード変更やDeprecatedのチェックからAPI通信などの通信チェックまで様々な側面が存在します。これは担当プロダクトの発展過程により異なるので、チーム全員でテストパターンを洗い出すことが大事なポイントです。

参考までに今回SCMチームで洗い出したパターンは以下となります。

  1. 社内用API 通信テスト
  2. 社外用API 通信テスト
  3. 社内管理画面及びコア機能の動作テスト
  4. 本番環境と同等のデータ量やトラフィックを想定したパフォーマンス測定
実行環境の確認

アプリケーションは使用中の言語バージョンを含めライブラリなど様々な依存関係を持ちます。実行環境の構成によっては依存関係の変化を同じく追従する作業が必要になるケースもあります。なので、プロダクトの実行環境の仕組みを把握してきちんと依存関係を同期させたり、依存関係に影響を受けない実行環境の構成を作る必要があります。

Goにはクロスコンパイル機能があり、実行環境毎にバイナリをビルドできるので、実行環境の影響を受けにくい言語です。実行環境のコンテナではこのバイナリを使うだけで、Goのバージョンやライブラリなど依存関係の変化による影響を最小化することができます。

実際SCMチームではこのようなGoの特徴とAWSサービスを組み合わせたデプロイ仕組みを導入しています。下記の図の通りデプロイの際にCodeBuildでプロダクトの依存関係を含めたバイナリーを生成し、コンテナイメージ化します。実行環境ではECRに保存されたバイナリーイメージを使うだけで依存関係の変化による影響を受けにくい構成をしています。

インフラ構成図

最後に

以上、Goのバージョンアップがサクッとできたお話でした。

SCMチームに来る前には依存関係の変更があった際に実行環境でも同じく依存関係のアップデートなどが求められるRubyやPHPを主に使っていたので、後方互換をちゃんと保っていてかつバイナリでコンパイルできるGo言語のバージョンアップは比較的楽でした。

今回Go言語の頼もしさと手順書の作成でバージョンアップからテストとリリースまで快適に完了できましたし、リリース後も問題が発生しなかったので今後のアップデートでも同じ手順で安全に行える自信が付きました。

言語のバージョンアップはつい身構えてしまうものですが、プロダクトの健康維持のためには必須の作業です。手順書作成や実行環境の整備などをしっかり行うことでより低コストかつメンテナンスが簡単な仕組みを作り、プロダクト作りに集中していけるとよいですね!