キャディ株匏䌚瀟のブログ - TECH PLAY

TECH PLAY

キャディ株匏䌚瀟

キャディ株匏䌚瀟 の技術ブログ

å…š240ä»¶

こんにちは、Drawer Growthグルヌプ ゜フトりェア゚ンゞニアの内田( id:usadamasa , @usadamasa )です。匊瀟ではApache Icebergの掻甚 *1 ずずもに、䞀郚のアプリケヌションにJavaを導入しおいたす。今回は、システムアヌキテクチャから䞀段レむダを䞋げおアプリケヌションレベルのお話しをしたいず思いたす。 アプリケヌションアヌキテクチャの蚭蚈ず運甚課題 アプリケヌション開発においお、私たち゚ンゞニアは通垞、パッケヌゞ構成やレむダの䟝存関係、ロギングなどの芳点からアヌキテクチャを蚭蚈したす。 しかし、実装ずの䞍敎合やチヌム内での共通認識が䞍十分なたた進むず、品質課題ずしお朜圚化し、やがお本番障害や開発者の疲匊ずいった圢で問題に発展したす。たた、DevinやClineなどのAI゚ヌゞェントに適切に実装しおもらうにはプロンプトやドキュメントで蚭蚈を䌝える必芁がありたすが、盞応の準備が求められたす。 このような課題を解決する手段ずしお、蚭蚈ず実装の敎合性をテスト可胜にするJavaラむブラリ「 ArchUnit *2 」がありたす。JUnitのフレヌムワヌク䞊で動䜜し、蚭蚈ルヌルを宣蚀的なコヌドで定矩しおテストずしお実行できるため、普段の自動テストず同様の迅速なフィヌドバックが埗られたす。 ArchUnitの導入の経緯ず効果 私たちのアプリケヌションは開発開始から玄半幎の若いコヌドベヌスで、初期はアヌキテクチャらしい構造もないプロトタむプからスタヌトしたした。開発が進む䞭で、パッケヌゞずレむダ構成を蚭蚈し、リファクタリングを重ねお敎理しおきたした。 次の図はそのレむダ構成です。「オブゞェクト蚭蚈スタむルガむド *3 」を参考に、Application局、Service局、Domain局、InfraStructure局の構成を採甚したした。 レむダ構成図 実装の倧郚分はこの構成に埓っおいたすが、初期のコヌドやレビュヌをすり抜けた箇所には、蚭蚈に適合しない郚分が残っおいたした。開発が䞀段萜し敎理を詊みたものの、実際にどの皋床のコヌドが䞍適合かは把握できおいない状況でした。 そこで、冒頭のArchUnitを導入し、䞍適合な実装の網矅的な怜出ず、今埌のルヌル遵守を継続的にチェックできる仕組みを構築したした。 ArchUnitでは、パッケヌゞやクラス間のアクセス、継承、埪環参照など様々なルヌル怜査が可胜です *4 。今回はその䞭でも「Layer Checks *5 」を甚い、定矩したレむダ構造ぞの準拠を確認したした。以降で、その具䜓的なルヌルず実装の䞀䟋を玹介したす。 初期バヌゞョン 「DAO局にアクセスできるのはRepositoryImpl局のみである」ずいう蚭蚈をコヌド化したしょう。 DAO( persist.dao パッケヌゞ)局はRDBなどのMWず盎接通信をする局です。Serviceå±€( service パッケヌゞ)はRepositoryå±€( domain.repository パッケヌゞ)をむンタフェむスずし、RepositoryImpl( persist.impl パッケヌゞ)がDAO局に盎接アクセスしたす。぀たり、DAO局に盎接アクセスできるのはRepositoryImpl局のみずいう蚭蚈です。この蚭蚈をArchUnitで蚘述するず、次のようになりたす。 *6 @AnalyzeClasses (packages = "com.example.sample" ) public class LayeredArchitectureTest { @ArchTest static ArchRule test_DaoMustBeAccessedOnlyFromRepositoryImpl = // DAO <- only repository impl layeredArchitecture().consideringAllDependencies() // <persist> DAO .layer( "DAO" ).definedBy( "com.example.sample.persist.dao.." ) // <persist> Repository Impl .layer( "RepositoryImpl" ).definedBy( "com.example.sample.persist.impl.." ) // Assert .whereLayer( "DAO" ).mayOnlyBeAccessedByLayers( "RepositoryImpl" ); } @AnalyzeClasses のアノテヌションず、フィヌルド倉数ずしおテストケヌスが蚘述されおいるこずに驚くかも知れたせんが、これはキャッシュ化のためで、本筋からは倖れるので、詳现は省きたす。 *7 Layer Checksは Architectures.layeredArchitecture に続いおメ゜ッドチェむンでルヌルを蚘述しおいきたす。怜査察象のレむダ名ず察応するパッケヌゞ名のセット( LayerDefinition )は耇数蚘述できたす。 LayerDefinition に続いお、どのレむダにどのようなルヌルが期埅されるのかを whereLayer("DAO").mayOnlyBeAccessedByLayers() のような圢で続けたす。これもメ゜ッドチェむンで耇数蚘述できたす。 さお、このテストケヌスはJUnit5のテストスむヌトずしお実行できたす。実行は高速で数秒で完了したす。 JUnitによる実行結果 さお、怜査に違反する堎合を芋お芋たしょう。サンプルずしおわざずDAO局のクラスをService局である ExampleQueryService から盎接呌び出す実装を曞いおみたす。 package com.example.sample.service; // ..snip.. public class ExampleQueryService { // FIXME ! This dependency must be removed!! ExampleDao exampleDao; // ..snip.. } この状態でJUnit5を実行するず、期埅通りテストが倱敗したした。少々芋慣れないメッセヌゞが出力されたすが、読み解くこずは容易です。 java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'Layered architecture considering all dependencies, consisting of layer ' DAO ' ( ' com.example.sample.persist.dao.. ' ) layer ' RepositoryImpl ' ( ' com.example.sample.persist.impl.. ' ) where layer ' DAO ' may only be accessed by layers [ ' RepositoryImpl ' ]' was violated ( 1 times): Field <com.example.sample.service.ExampleQueryService.exampleDao> has type <com.example.sample.persist.dao.exampleDao> in (ExampleQueryService.java: 0 ) (StackTrace省略) AssertionError ずしおArchitectureの違反(Violation)が報告され、5行目の Field ~~ が具䜓的な違反コヌドぞの指摘ずなりたす。 さおサンプルコヌドがやや長くなりたしたが、このような圢で蚭蚈意図をルヌルずしお蚘述し、JUnitずしお実行できるこずがおわかりいただけたかず思いたす。 チヌムぞの共有ずルヌルの改善 さお、このようなテストコヌドを曞き、実際に修正に入る前に導入に぀いおチヌムに共有したずころ、このようなフィヌドバックをもらいたした。 チヌムからのフィヌドバックコメント チヌムメンバの蚀うずおり、たしかに私はこの蚭蚈に぀いお把握しおおらず、ずおも有益な情報でした。ではこの蚭蚈をArchUnitに反映させたしょう。先ほどのテストケヌスに远加・線集したす。 @AnalyzeClasses (packages = "com.example.sample" ) public class LayeredArchitectureTest { // [ADDED] QueryService allow to access DAO directly static DescribedPredicate<JavaClass> QUERY_SERVICE_PREDICATE = resideInAPackage( "com.example.sample.service.." ) .and(nameEndingWith( "QueryService" )); @ArchTest static ArchRule test_DaoMustBeAccessedOnlyFromRepositoryAndQueryService = // DAO <- only repository impl layeredArchitecture().consideringAllDependencies() // <persist> DAO .layer( "DAO" ).definedBy( "com.example.sample.persist.dao.." ) // <persist> Repository Impl .layer( "RepositoryImpl" ).definedBy( "com.example.sample.persist.impl.." ) // [ADDED] <service> Query Service .layer( "QueryService" ).definedBy(QUERY_SERVICE_PREDICATE) // Assert .whereLayer( "DAO" ).mayOnlyBeAccessedByLayers( "RepositoryImpl" , "QueryService" // [ADDED] ); } DescribedPredicate が新しく登堎したした。これは、独自のルヌルを蚘述するためのAPIであり、and条件、or条件や様々な評䟡方法を組み合わせるこずができたす *8 。ここではService局のなかでも、クラス名が QueryService で終わるもののみDAO局にアクセスできるずいうルヌルを远加しおいたす *9 。これにより先ほど違反ずしお怜出されたコヌドも、適合しおいるず刀定されるようになりたした。あずは残った違反コヌドを修正すれば終わりです *10 。 このような圢で圓初の課題であったアプリケヌションアヌキテクチャの継続的か぀自動的なチェックが可胜になりたした。 ArchUnitの所感 ここからはArchUnitの意矩や所感に぀いおやや雑倚に述べようず思いたす。 よいずころ 自然蚀語で蚘述した蚭蚈ルヌルや意図をコヌドずしお萜ずし蟌めるこず PRのレビュヌで初めお知った、が防げる 解釈のブレも抑えられる フィヌドバックが高速か぀JUnitに統合されおいるこず 远加の手順が䞍芁で実行挏れが防げる CIパむプラむンにそのたた茉せられる 画像のようにGitHub ActionsのJUnit Test Reportで通知も可胜 GitHub ActionsでのJUnit Test Report 気になるずころ/所感 䞀方で、ArchUnitにはいく぀か泚意すべき点もありたす。特に宣蚀的でDSL(ドメむン特化蚀語)色が匷いため、習埗・定着には䞀定のコストがかかるず感じたした。 䞭でも DescribedPredicate を甚いおルヌルをカスタムする堎合には、ルヌルそのものの動䜜怜蚌が必芁になるため、実装には慎重さが求められたす。 このため、ArchUnitで蚘述するルヌルは、蚭蚈意図に基づいたものに限定し、必芁以䞊に耇雑な実装は避けるべきでしょう。 䞀般的なコヌディング芏玄や静的解析ルヌルに぀いおは、SpotBugs *11 やSonarQube *12 のようなプリセットの揃ったLinterず䜵甚するのが望たしいず感じたした。 たた、ArchUnitでは埪環的耇雑床などの構造的な指暙を定量的に評䟡し、ルヌル化できたす *13 。 しかし、あくたで単䞀のコヌドベヌスに察するLinterであり、党瀟的な品質管理やチヌム暪断的な指暙の可芖化には向きたせん。 クオリティマネゞメント職やマネヌゞャヌ局向けには、別途暪断的に可芖化できるツヌルの導入を怜蚎すべきでしょう。 ArchUnitに過床な期埅を抱かず、その圹割を正しく芋極めたうえで、目的に応じお他のツヌルず適切に䜿い分けるこずが重芁です。 結びに替えお: 生成AIず゜フトりェアアヌキテクチャ ここから先はArchUnitずは盎接関係のない䞎倪話なので話半分で読んでいただけるず幞いです。 ここ数ヶ月で、゜フトりェア゚ンゞニアの開発環境に生成AIが急速に浞透し぀぀ありたす。 生成AIず協働するコヌディング掻動を Vibe Coding *14 ず呌ぶようになっおただ新しいですが、 AIのコヌド生産量に人間が远い぀けなくなるのはそう遠い未来ではないでしょう。 人間によるコヌドレビュヌが困難になったずしおも、機胜品質は埓来の自動テストやE2Eテストでの担保や、そもそも゜フトりェア゚ンゞニアを介さずプロダクトマネヌゞャヌなどの怜蚌で十分になるかもしれたせん。では非機胜品質は、内郚品質は、゜フトりェアアヌキテクチャはどのように担保出来るでしょうか? そもそもこの先゜フトりェアアヌキテクチャにその存圚意矩はあるでしょうか? 話が倧きくなりたすが、生成AIによる゜フトりェア開発に即したアヌキテクチャずその評䟡指暙、぀たり 生成AIを前提ずしたアヌキテクチャ適応床関数 *15 の発明が必芁なのだず思いたす。 生成AI自身も゜フトりェアであり、実行にはサヌバのリ゜ヌスや時間、぀たり電力を必芁ずしたす *16 。 それらは有限か぀垌少な資源です。人類が無限の゚ネルギヌを手に入れるにはただだいぶ掛かるでしょう。 ずなるず、AIの消費゚ネルギヌ *17 を節玄するアヌキテクチャ *18 が求められ、それを枬定・評䟡する指暙を元に開発するようになるのではないでしょうか。もしかしたらそれは、人間がいたたで考え出しおきた゜フトりェア蚭蚈・プラクティスずそんなに倉わらないのかも知れたせん *19 。 加えお、AIぞのフィヌドバックには網矅的、高速か぀自動的な怜査が必芁になりたす。2025幎珟圚、䞀般的な開発組織ではアヌキテクチャ適応床関数が十分に浞透しおいるずは蚀いがたい状況かもしれたせん。圓然その自動化も発展の䜙地がありたす。本蚘事はアプリケヌションアヌキテクチャの怜蚌が䞻題でしたが、生成AIによる゜フトりェア開発がより高いレむダに組み蟌たれるに぀れお、それに合わせたより抜象床の高い適応床関数を甚いおいく必芁が出おくるかもしれたせん。 そのような゜フトりェア蚭蚈掻動は䟝然ずしお゚キサむティングか぀創造的であり、゜フトりェア゚ンゞニアずいう職はただただ面癜いものであり続けるのではないかず期埅しおいたす。 さお、いい加枛個人ブログに曞けず蚀われかねないためそろそろお決たりの蚀葉で締めさせおください。キャディ株匏䌚瀟では補造業AIデヌタプラットフォヌムずしお成長しおいくために゚ンゞニアの採甚を加速しおおりたす。生成AIが発展しおいっおも゜フトりェア゚ンゞニアずしおただただやっおいきたいずいう方は、ぜひご連絡ください! recruit.caddi.tech ArchUnitに関する詳现な解説・先行事䟋 ArchUnitでアヌキテクチャの単䜓テストを行う ArchUnitでアヌキテクチャをテストする - mtx2s’s blog ArchUnit をマルチモゞュヌル構成に察しお適甚する | omuomugin ArchUnit䜿い方メモ #Java - Qiita *1 : キャディでの Apache Iceberg 掻甚事䟋 , Apache IcebergずCDCによるデヌタレむクハりス拡匵 , 氷山を穿぀ - Apache Icebergに倧量デヌタを投入するTopic *2 : Unit test your Java architecture - ArchUnit *3 : オブゞェクト蚭蚈スタむルガむド - O'Reilly Japan *4 : ArchUnit User Guide 4. What to Check *5 : ArchUnit User Guide 4.6 Layer Checks *6 : pomやgradleの蚘述などはリファレンスを参考にしおください。 *7 : ArchUnit User Guide 3.3. Using JUnit 4 or JUnit 5 *8 : ArchUnit User Guide 7.3. Creating Custom Rules *9 : 特別なルヌルはコメントや背景を付蚘するず良いでしょう。テストもコヌドです。 *10 : 実際はArchUnitの導入を先に行い、既存の違反コヌドは Freezing Arch Rules でマヌク、埐々に違反コヌドを削枛しおいきたした。 *11 : SpotBugs *12 : Better Code & Better Software | Ultimate Security and Quality | Sonar *13 : ArchUnit User Guide 8.7. Software Architecture Metrics *14 : Vibe coding - Wikipedia ここでは広矩のVibe codingを意図しおいたす。 *15 : ゜フトりェアアヌキテクチャメトリクス - O'Reilly Japan *16 : 【提蚀】生成AIの普及が䞎える日本の電力需芁ぞの圱響 *17 : DevinならACU *18 : awslabs/mcp: AWS MCP Servers — specialized MCP servers that bring AWS best practices directly to your development workflow *19 : AI-friendly code design | Technology Radar | Thoughtworks
はじめに はじめたしお、Drawerグルヌプ所属の もりや です。 キャディは入瀟しお玄2幎になりたすが、ブログ蚘事を曞くのは初めおです。よろしくお願いしたす。 私は入瀟時から 補造業デヌタ掻甚クラりドCADDi Drawer の開発に携わっおおり、最初のRBACベヌスの認可を私が䞭心に実装したした。 その関係から、今はIDチヌムで認蚌認可呚りの開発を担圓しおたす。 今回は、CADDi DrawerでSSOをサポヌトしたこずに぀いお、䞻にAuth0の芳点で曞きたす。 おこずわり この蚘事は、Auth0をある皋床䜿ったこずがある方向けに曞いおいたす。 タむトルに「1幎かけお」ずありたすが、開発着手からリリヌスたでの期間を指しおいたす。 途䞭で他の機胜開発をしおいた期間も含たれおおり、䞞々1幎を党お開発に費やしたわけではない点にご留意ください。 CADDi Drawer ずは 認蚌芳点で簡単に曞くず、CADDi Drawerはマルチテナント構成のSaaSで、認蚌にはAuth0ずいう認蚌・認可のプラットフォヌムを䜿甚しおいたす。 アプリケヌション偎は、Next.jsず nextjs-auth0 ずいうラむブラリを䜿っおAuth0を利甚しおいたす。 1぀のAuth0テナントに、耇数のCADDi Drawerのテナントが存圚する構成になっおいたす。 CADDi Drawerは 倚くの倧䌁業でも䜿甚いただいおいたす。 傟向ずしお、ナヌザヌ数が倚いテナントほどSSOを求める声は倚くあり、今回はその芁望に応えるためにSSOを実装したした。 補足ずしお、以前に 独自の認蚌認可基盀を開発 しおいたこずもありたしたが、今のずころはAuth0を䜿い続けおいたす。 本蚘事の構成 倧たかに以䞋のような構成になっおいたす。 怜蚎: Auth0でSSOを提䟛するために怜蚎したこず 実装: Auth0を䜿っおSSOを実装したこず 今埌: 䜜りたい機胜や課題など 時系列順には曞いおいないので、その点もご留意ください。 たた、文字数が倚くなりそうだったためかなり端折っおいたす。 もっず现かい郚分も聞いおみたいず思われた方は、ぜひ カゞュアル面談 で話したしょう。 1. 怜蚎 Auth0 での SSO の提䟛方法Auth0 Organizations CADDi Drawerで求められるSSOは、Googleログむンのように党ナヌザヌに提䟛するものではなく、契玄しおいる各䌁業のIdPを、特定のテナントのみに玐付けおSSOを提䟛したす。 そのため、単にアプリケヌション党䜓で提䟛するのではなく、テナント単䜍に蚭定する必芁がありたす。 調査した結果、こうした堎合はAuth0 Organizationsずいう機胜を䜿うず良いずいう結論に至りたした。 Auth0 Organizations ずは Auth0 Organizationsずいう機胜を利甚するこずで、耇数の組織を管理し、組織ごずの蚭定SSOなどが簡単に実珟できたす。 デヌタ構造ずしおは、Organization= CADDi Drawerのテナントずいう情報が新たに登堎し、そこにナヌザヌ情報を玐づけるずいう圢になりたす。 補足ずしお、これたでは app_metadata を䜿っお所属するテナントを管理しおいたした。 Auth0 Organizationsは、このような独自の管理を䜿わずに、Auth0だけで組織を管理できたす。 Auth0 Organizations を䜿った堎合のトヌクンの倉曎 Auth0から発行されるIDトヌクン、アクセストヌクンに org_id ずいうクレヌムが远加されたす。 たた、蚭定を倉曎するこずで org_name ずいうクレヌムも付䞎されたす。 これらの情報を䜿うこずで、アプリケヌション偎でもどのOrganizationテナントに所属しおいるナヌザヌなのかを刀断できたす。 ログむン䜓隓の決定 Auth0 Organizationsを䜿うず、Auth0 Applicationの単䜍でログむンの䜓隓を決定できたす。 ここはナヌザヌ䜓隓に重芁なポむントになるので、PdMやCSチヌムず盞談しながら決定したした。 Type of Users ログむン可胜なナヌザヌの皮類です。以䞋の぀がありたす。 Individuals : Auth0 Organizationsを䜿わない Business Users : Auth0 Organizationsが必須 Both : どちらでも可 Auth0 Organizationsを䜿う堎合は Business Users たたは Both を遞択する必芁がありたす。 CADDi Drawerでは、党おのナヌザヌは䜕らかのOrganizationに所属する事を必須にしたいので Business Users を遞択したした。 Login Flow ログむンフロヌの遞択肢です。以䞋の぀がありたす。 Prompt for Credentials : 最初にナヌザヌはメヌルアドレスを入力し、メヌルドメむンに基づいおログむン方法 (Auth0 Connection) を決定しログむンする方匏 Prompt for Organization : 最初にナヌザヌがOrganizationを入力し、該圓するOrganizationにログむンする方匏 No Prompt : 呌び出し偎のアプリケヌションで刀断する方匏 Prompt for Credentials は䞀般的によく芋られる圢匏で、既存の䜓隓ずも倧きく倉わらないので䞀芋良い方法に芋えたした。 しかし、この䜓隓を実珟するAuth0のHome Realm Discovery (HRD) がCADDi Drawerずしおは採甚しづらいものでした。 HRDは、ナヌザヌが入力したメヌルアドレスではなく、メヌルアドレスの「ドメむン」に基づいお、ログむン方法Auth0 Connectionを刀断したす。 䟋えば1぀の䌁業で耇数のテナントを契玄しおいる堎合だず、SSOの提䟛に支障が発生する可胜性があったため、HRDを䜿う刀断はしたせんでした。 Prompt for Organization は、ナヌザヌが最初にOrganizationを入力する必芁があるため、ナヌザヌ䜓隓ずしおはあたり良くありたせん。 たた、特にナヌザヌ数が倚いテナントの堎合、組織名を呚知するのも倧倉ずいうオペレヌションの課題もありたす。 よっお、こちらも採甚したせんでした。 このあたりは色々ず怜蚎したのですが、最終的には以䞋のような結論になりたした。 パスワヌドでログむンするナヌザヌにはこれたで通りの䜓隓を提䟛する SSOを利甚するナヌザヌには、SSO専甚のURLを䜿っおログむンしおもらう Auth0の蚭定ずしおは Prompt for Credentials を遞択し぀぀、アプリケヌション偎で専甚URLでない堎合はパスワヌドでのログむンを行えるように制埡したした。 nextjs-auth0を䜿ったコヌドのむメヌゞは以䞋のずおりです※実際に皌働しおいるコヌドずは異なりたす // 参考: https://github.com/auth0/nextjs-auth0/issues/701#issuecomment-1255350171 import { handleAuth, handleLogin } from "@auth0/nextjs-auth0" ; const login = async ( req , res ) => { // 1. ク゚リパラメヌタヌから Auth0 Organization の情報を取埗 const organization: string | undefined = req.query. organization ; // 2. Auth0 Organization が指定されおいない堎合は、パスワヌドでのログむンを匷制 const connection = organization === undefined ? undefined : "Username-Password-Authentication" ; await handleLogin(req, res, { authorizationParams : { organization , connection , } , } ); } ; export default handleAuth( { login } ); このようにするこずで、既存の䜓隓の維持ず、SSOの提䟛を䞡立させるこずができたした。 ナヌザヌデヌタの䜜り方 SSOを提䟛した堎合、単玔に䜜るず同䞀のメヌルアドレスで以䞋の皮類のナヌザヌが存圚可胜になりたす。 パスワヌドでのログむンを行うナヌザヌ以䞋「パスワヌドナヌザヌ」 SSOでログむンするナヌザヌ以䞋「SSOナヌザヌ」 これは、Auth0のConnectionが異なる堎合は、䞀意性制玄が効かないためです。 Auth0のConnection単䜍で芋れば䞀意になりたす メヌルアドレスずしおは同じナヌザヌに芋えたすが、ナヌザヌIDは異なりたす。 CADDi Drawerでは、様々なデヌタをナヌザヌIDベヌスで管理しおいるので、システムずしおは別のナヌザヌに芋えたす。 これは、個人の蚭定情報が共有できない、利甚状況を把握しづらくなる、など色々ず問題になりそうです。 メヌルアドレスをIDずしお管理できるようにすれば解決できそうですが、倧きな改修が必芁になるため、今回は芋送りたした。 これも色々怜蚎した結果、以䞋のような構成に萜ち着きたした。 党おのナヌザヌは、パスワヌドでのログむンUsername-Password-Authenticationをベヌスに䜜成する User Account Linking を䜿っお、䞊蚘のナヌザヌにログむン方法だけ远加するような圢匏にする パスワヌドでのログむンは、Auth0の機胜的にOFFにできないので、Auth0 Actionsで制埡する こうするこずで、SSOを䜿う堎合でもメヌルアドレスに察しお必ず1぀のAuth0ナヌザヌずなりたした。 初回のログむン凊理時にUser Account Linkingをしたり、ログむン方法をチェックするなど、倚少耇雑にはなりたす。 しかし、メヌルアドレスに察しおナヌザヌが䞀意になるので、システムずしおはシンプルに扱えるようになりたした。 2. 実装 Auth0 Organizations ぞの移行ずドメむン移行 SSOのサポヌトのために、たずはAuth0 Organizationsぞの移行したした。 倧きな倉曎になるので、既存のログむンずAuth0 Organizationsを䜿った新しいログむンの䞡方のどちらも䜿える䞊行期間を蚭け、慎重に行いたした。 たた、同時にCADDi Drawerのドメむン移行の蚈画もあったため、SREチヌムず連携しながらナヌザヌに圱響を䞎えないように進めたした。 既存ナヌザヌのデヌタ移行 既存のデヌタベヌスにあるテナント情報から、Auth0にOrganizationを䜜り、所属するナヌザヌのデヌタを玐づけおいきたす。 Organizationに玐づける情報を远加するだけで、既存のナヌザヌデヌタには倉曎が発生しないので、割ず気楜にできる䜜業でした。 移行は、以䞋のAPIを組み合わせお、党お自動で行うこずができたす。 党ナヌザヌ情報の取埗: https://auth0.com/docs/manage-users/user-migration/bulk-user-exports Organizationの䜜成: https://auth0.com/docs/api/management/v2/organizations/post-organizations Organizationずナヌザヌの玐づけ: https://auth0.com/docs/api/management/v2/organizations/post-members 泚意点ずしおAuth0のRate Limitがそれなりに厳しいです参考: Enterprise のRate Limit。 本番で皌働しおいるので、安党に移行できるように、1リク゚ストごずにスリヌプ凊理を入れ、Rate Limitに匕っかからないようにしたした。 Next.js で耇数の Auth0 Application の察応 たずNext.js偎では nextjs-auth0 の蚭定を2぀甚意し、Auth0クラむアントのむンスタンスを切り替えるこずで、ログむンの䞊行皌働を実珟したした。 具䜓的には、HTTPの Host ヘッダヌに応じおAuth0のドメむンずクラむアントIDを切り替えるこずで、新旧どちらのAuth0 Organizationsにも察応できるようにしたした。 実際のやり方ずしおは、たずAuth0のApplicationを2぀甚意したす。 既存のAuth0 Organizationsが「無効」なApplication 新芏のAuth0 Organizationsが「有効」なApplication nextjs-auth0 は initAuth0 ずいう関数を䜿っおAuth0のむンスタンスを初期化でき、それぞれのApplicationに察応するAuth0のむンスタンスを甚意したす。 より具䜓的な実装で蚀うず、以䞋のようなむメヌゞでAuth0のむンスタンスを切り替えお枡しおいたした※実際に皌働しおいるコヌドずは異なりたす import { initAuth0 } from '@auth0/nextjs-auth0' ; export const getAuth0 = ( req ) => { const host = req. headers [ 'Host' ]; if (host === NewDomain) { return initAuth0( /* 新ドメむン甚の Auth0 蚭定 */ ); } return initAuth0( /* 旧ドメむン甚の Auth0 蚭定 */ ); } ; 停止を䌎う移行 以䞋の蚭定に぀いおは䞊行皌働できないので、サヌビス停止を行っお䞀斉に移行したした。 Auth0のカスタムドメむン Auth0が発行したAccess Token (JWT) の怜蚌に぀かうJWKsのURL 特に難しい点はなく、移行手順の準備や、Staging環境でのリハヌサルを行っおいたため、倧きな問題なく移行を完了できたした。 補足ずしお、先に曞いた通りドメむン移行ず同時に行っおいたので、このタむミングで旧ドメむン→新ドメむンぞのリダむレクト察応なども行っおいたす。 ここたでで、ナヌザヌに気づかれるこずなく、ナヌザヌデヌタの構造倉曎を完了させたした。 SSO 機胜の実装 ここが本題ですが、実際のずころSSO機胜そのものはAuth0に任せるので、CADDi Drawer固有の実装が少しあった皋床でした。 䞻に、Auth0 Actionsでの開発になりたす。 なお、CADDi Drawerのナヌザヌ管理機胜などの開発に぀いおは省略したす。 パスワヌドでのログむンが犁止されおいるかのチェック 「ナヌザヌデヌタの䜜り方」で説明した通り、党おのナヌザヌはパスワヌドでのログむン方法を持ちたす。 しかし、組織ポリシヌでパスワヌドでのログむンを犁止したいケヌスも発生するこずが想定されおいたす。 Auth0のナヌザヌの app_metadata に、パスワヌドでのログむンが犁止されおいるかどうかのフラグをもたせたす。 Auth0 Actionsのコヌドのむメヌゞは以䞋のずおりです※実際に皌働しおいるコヌドずは異なりたす exports .onExecutePostLogin = async ( event , api ) => { const isDisablePasswordLoginUser = event.user.app_metadata.disablePasswordLogin === true ; const isLoginWithPassword = event. connection .strategy === "auth0" ; if (isDisablePasswordLoginUser && isLoginWithPassword) { // ※泚意: api.session.deny だず Auth0 セッションが残り続け、自発的に Cookie を消さない限りログむンできなくなる api.session.revoke( "disallow_password_login" ); return ; } } ; SSO の初回ログむン時の User Account Linking 凊理 「ナヌザヌデヌタの䜜り方」で説明した通り、党おのナヌザヌはパスワヌドでのログむン方法を持ちたす。 よっお、SSOで初回ログむンした埌に、User Account Linkingを行い、パスワヌドナヌザヌず統合する凊理が必芁になりたす。 前提ずしお、ナヌザヌを招埅する時に app_metadata.linkUserTo に統合先のナヌザヌIDを蚭定しおおきたす。 そしお、以䞋のようなAuth0 Actionsを実装し、User Account Linkingを行いたす※実際に皌働しおいるコヌドずは異なりたす exports .onExecutePostLogin = async ( event , api ) => { // User Account Linking が必芁か刀定 const loginUserId = event.user.user_id; const linkUserTo = event.user.app_metadata?.linkUserTo; if (linkUserTo == null || linkUserTo === loginUserId) { return ; // Skip } const primaryUserId = linkUserTo; const secondaryUserId = loginUserId; const secondaryUserProvider = event.user.identities[ 0 ].provider; // Auth0 Organization が䞀臎するかのチェック const managementApiClient = "(省略)" ; const {data : primaryUserOrganizations } = await managementApiClient.users.getUserOrganizations( { id : primaryUserId } ); const primaryUserOrganizationIds = primaryUserOrganizations. map (( org ) => org. id ); const secondaryUserOrganizationId = event. organization . id ; if (!primaryUserOrganizationIds. includes (secondaryUserOrganizationId)) { api.access.deny( "(Auth0 Organization の䞍䞀臎゚ラヌ)" ); return ; } // User Account Linking の実行 await managementApiClient.users. link ( { id : primaryUserId } , { user_id : secondaryUserId, provider : secondaryUserProvider } , ); // ※䞋蚘で解説 api.access.deny( `request_re-login: ${ JSON . stringify ( { organization : event. organization . name , connection : event. connection . name } ) } ` , ); } ポむントは、User Account Linking成功埌に api.access.deny でカスタマむズした゚ラヌを返す郚分です。 たず前提ずしお、このログむンフロヌは「SSOナヌザヌ」でログむンした状態ですが、終了時点では「パスワヌドナヌザヌ」に統合され、「SSOナヌザヌ」は存圚しなくなりたす。 なので「パスワヌドナヌザヌ」に切り替える必芁がありたす。 実は api.authentication.setPrimaryUser() ずいうメ゜ッドも甚意されおいたすが、今回のCADDi Drawerの蚭定では䜿えたせんでした。 以䞋を読む限り、Auth0 Organizationsで "Prompt for Credentials" を䜿甚しおいる堎合は api.authentication.setPrimaryUser() を利甚できないようです。 Error: Organizations is Not Supported Together with Primary User Modifications in Rules - Auth0 Community 苊肉の策ずしお、カスタマむズした゚ラヌに organization ず connection の情報を持たせ、Next.js偎でハンドリングしお、再床ログむンする、ずいう方法を取っおいたす。 補足するず、再床ログむンするずいっおもSSOなので、通垞は䜕床かリダむレクトを挟むだけでナヌザヌの操䜜は䞍芁です。そこたで倧きな䜓隓の悪化はありたせん。 デヌタの䞍敎合チェック 今回採甚した蚭定だず、Auth0 Organizationsに所属しおいないナヌザヌはログむンができなくなりたす。 䞇䞀そういったデヌタが発生した堎合に備え、䞍敎合を怜出するスクリプトを䜜っお自動でチェックをできるようにしたした。 このスクリプトは、GitHub Actionsで毎日実行するように蚭定しお、䜕かあれば通知が来るようにしたした。 日次なのでリアルタむム性は若干䜎いものの、これによっおデヌタに問題はないこずを毎日確認でき、安心できたした。 ちなみに、この蚘事を曞いおいる時点では、テストデヌタなどを陀いお䞍敎合は発生しおいたせん。 3. 今埌 ログむン䜓隓の向䞊 珟状では、テナントごずにSSO専甚のURLを発行し、それを䜿甚した堎合のみSSOでログむンできたす。 これはログアりト埌に再床SSOでログむンするするこずができないので、ナヌザヌ䜓隓が悪いなどの問題がありたす。 たた、既存のナヌザヌにSSO専甚URLが浞透しづらいずいうオペレヌションの課題もありたす。 専甚URLなしでもSSOでログむンできるように、ログむンフロヌを芋盎しおいたす。 ナヌザヌ自身でのSSOの蚭定 珟圚は、キャディ偎でSSOを蚭定しおいたすが、ナヌザヌ自身でSSOの蚭定を行えるようにしたいず考えおいたす。 Self-Service Single Sign-On を䜿えないか怜蚎しおいたす。 おわりに 調査や䞋準備など含めるず長い開発でしたが、ひずたず無事にSSOを提䟛できおよかったです。 ただただ課題はありたすが、少しず぀改善しおいきたいず思いたす。 We are hiring! キャディでは認蚌認可領域の゚ンゞニアも絶賛募集䞭です Senior Software Engineer, Backend - 認蚌認可 - / キャディ株匏䌚瀟 recruit.caddi.tech
こんにちは、Data&Analysis郚(D&A)です。 D&Aでは週1回、機械孊習の勉匷䌚を開催しおおり、本蚘事は、勉匷䌚の内容を生成AIを掻甚しお蚘事にたずめたものです。 ※勉匷䌚内容公開の経緯は こちら ※過去の勉匷䌚は「瀟内勉匷䌚」タグからもご芧いただけたす。 抂芁 GraphRAGの抂芁 RAGの抂芁ず課題 GraphRAGの基本的な考え方 ナレッゞグラフずは アルゎリズムの抂芁 暙準的なGraphRAG MicrosoftによるGraphRAG GraphRAGの評䟡 ナレッゞグラフの評䟡 GraphRAGの評䟡 ナレッゞグラフ / GraphRAGの課題 ナレッゞグラフの課題 GraphRAGの課題 参考文献 抂芁 今回の勉匷䌚では、ナレッゞグラフ埌述ずRAGRetrieval-Augmented Generationを組み合わせた技術であるGraphRAGに぀いお調査したした。 調査の動機は、瀟内でRAGを甚いた゜リュヌションの怜蚎が進められおおり、さらなる゜リュヌションの創出に向けた䞀案ずしおGraphRAGが挙げられおいたためです。 本蚘事では、GraphRAGのナヌスケヌス、アルゎリズムの抂芁、評䟡方法、そしお課題に぀いお玹介したす。 GraphRAGの抂芁 RAGの抂芁ず課題 埓来のRAGは、質問ず意味的に類䌌する内容コンテキストをベクトル怜玢によっお抜出し、質問ずコンテキストをプロンプトずしおLLMLarge Language Modelに入力するこずで回答を生成したす。 しかし、ドメむン知識が関䞎する質問、特に物事間の関係性が重芁な質問に察しおは、関連性の䜎い内容を出力しおしたうこずがありたす。 原因は回答を生成する過皋で行う文章のチャンク化の際、ドメむン知識を衚す文章が含たれおいない可胜性があるためずされおいたす。 GraphRAGの基本的な考え方 GraphRAGは、RAGの課題を解決するために、ナレッゞグラフを利甚したす。 質問のドメむン知識ず合臎する回答をナレッゞグラフから出力し、それをコンテキストずするこずで、質問により関連した内容を芋぀け出せるこずが期埅できたす。 実務においおは、匷みず匱みを補い合うためにGraphRAGを党文怜玢やRAGず䜵甚するこずが効果的です。 GraphRAGの基本的な流れは、自然蚀語である質問をグラフデヌタベヌス甚のク゚リ蚀語に倉換し、デヌタベヌスに問い合わせるこずでコンテキストを取埗し、質問ずコンテキストをLLMに入力しお回答を埗るずいうものです。 ナレッゞグラフずは ナレッゞグラフずは、゚ンティティノヌド同士をリレヌション゚ッゞで繋いだグラフの集合のこずです。 情報の関係性をグラフずいう繋がりで衚珟するため、RAGよりもドメむン知識を加味した回答を出しやすくなりたす。 䟋えば、「日本の銖郜は東京」ずいう関係性は、"日本"ず"東京"ずいう゚ンティティが"銖郜"ずいうリレヌションで繋がったグラフずしお衚珟されたす。゚ンティティにはプロパティ属性を持たせるこずも可胜です。 アルゎリズムの抂芁 GraphRAGのアルゎリズムは、倧きく分けお䞋図の暙準的なGraphRAGずMicrosoftによるGraphRAGの2皮類がありたす。 暙準的なGraphRAG むンデクシング時: テキストなどの非構造化デヌタからLLMを甚いお゚ンティティずリレヌションを抜出し、グラフ構造に倉換しおグラフデヌタベヌスに栌玍したす。 怜玢時: 質問をLLMによっおグラフデヌタベヌス甚のク゚リ蚀語䞀般的にはCypher、他にGremlinやSPARQLなどに倉換し、グラフデヌタベヌスに問い合わせを行いたす。 グラフデヌタベヌスずしおは、 Neo4j や Amazon Neptune などが䟋ずしお挙げられたす。 LangChain などのフレヌムワヌクを利甚しお実装するこずも可胜です。 MicrosoftによるGraphRAG MicrosoftによるGraphRAGは、暙準的なGraphRAGずは異なり、怜玢時にク゚リ蚀語ぞの倉換を行いたせん。 むンデクシング時: LLMを甚いおテキストから゚ンティティずリレヌションを抜出し、それらに玐づく文章の芁玄察応するベクトルをデヌタベヌスに栌玍したす。 さらに、コミュニティ怜出アルゎリズムを甚いお内容が類䌌する゚ンティティをグルヌプ化このグルヌプのこずをコミュニティず呌ぶし、コミュニティ内の゚ンティティに玐づく文章を芁玄したコミュニティレポヌトず呌ばれるものを䜜成したす。 怜玢時: 広い範囲の話題に぀いお抂芁を知るこずをを目的ずしたグロヌバル怜玢䟋LLMに぀いお教えお䞋さいず、特定の話題に関しお詳しく知るこずをを目的ずしたロヌカル怜玢䟋OpenAIが提䟛しおいるLLM別に、その特城ず埗意・苊手なタスク、掻甚事䟋を教えお䞋さいの2぀の方法質問に答えたす。 グロヌバル怜玢では、各コミュニティレポヌトず質問に察する回答ず重芁床を出力し、重芁床の高い回答をLLMのコンテキストにしお回答を生成したす。 ロヌカル怜玢では、゚ンティティの文章ず質問文ずのベクトル怜玢で意味が近い゚ンティティを抜出し、類䌌した゚ンティティに関連する情報をLLMが芁玄しお回答したす。 GraphRAGの評䟡 ナレッゞグラフの評䟡 評䟡方法の䟋ずしお以䞋のようなものがありたす [6] 。 GNNGraph Neural Networkを甚いおナレッゞグラフをベクトル化し、䞋流タスク分類、ベクトル怜玢などの粟床で評䟡する。 正解デヌタから重芁な単語や゚ンティティを抜出し、その単語に関する内容がナレッゞグラフにどれだけ含たれおいるかを評䟡する。 ある゚ンティティのサブカテゎリになるものがどれだけ含たれおいるか゚ンティティの倚様性を評䟡する。 䟋人ずいう゚ンティティに察しお、友人、芪子などの関連性のある゚ンティティがナレッゞグラフにどれだけ存圚するか。 GraphRAGの評䟡 GraphRAGの評䟡方法ずしおは、䞻に以䞋の2぀がありたす。 人手で䜜成した質問・回答ず、GraphRAGが出力する内容ずの関連性を人手評䟡する。 LLMの評䟡手法を利甚する䟋 ragas 、 LLM as a judge など。 MicrosoftのGraphRAGでは、LLMによっお回答を評䟡する手法が甚いられおいたす。 ナレッゞグラフ / GraphRAGの課題 ナレッゞグラフの課題 同䞀内容の゚ンティティの統合: 衚蚘揺れやデヌタ゜ヌスの重耇などにより、同䞀内容の゚ンティティが耇数存圚しおしたうため、それらを統合する必芁がありたす。 取り組みの䟋ずしお、䜕らかのルヌルに基づいお統合する手法や、゚ンティティに関する情報を特城量ずし、゚ンティティの分類タスクを解き、同じ分類結果ならば゚ンティティを統合するずいう詊みもありたす。 情報の正確性の担保: 信頌できる情報源からデヌタを取埗するこず以倖に、ナレッゞグラフの情報の正確性をどのように担保するかが課題ずなりたす。 グラフの曎新の難しさ: 既存のデヌタ゜ヌスに含たれない情報䟋バズワヌド、知識の倉化䟋補品名が倉わったを既存のグラフずの敎合性や既存のグラフ同士の関係性を保ったうえで曎新させるのが難しいずいう課題がありたす。 GraphRAGの課題 トヌクン料の増倧: ゚ンティティ・リレヌション抜出など、様々な凊理でLLMを䜿甚するため、トヌクン料が膚倧になる可胜性がありたす。 察策ずしお、 Triplex などの゚ンティティ・リレヌション抜出を効率化するツヌルも存圚したす。 その他の課題: 䞊蚘のナレッゞグラフ自䜓の課題も、GraphRAGの品質に圱響を䞎えるため、GraphRAGの課題ず蚀えたす。特に、同䞀内容の゚ンティティの統合に぀いおは、 MicrosoftのGraphRAGに該圓のフラグを蚭定する機胜が蚈画されおいたす が、本蚘事執筆時点2025幎4月では未実装です。 参考文献 [1] GraphRAGをわかりやすく解説 [2] 話題のGraphRAG、その可胜性ず課題を理解する [3] 話題のGraphRAGずは - 内郚構造の解析ず実甚性の考察 [4] From Local to Global: A Graph RAG Approach to Query-Focused Summarization [5] Welcome to GraphRAG [6] Structural Quality Metrics to Evaluate Knowledge Graphs
こんにちは、 Drawer Growth グルヌプの倧朚です。 キャディでは、膚倧な図面デヌタを効率的に怜玢・掻甚できるよう、Elasticsearchを掻甚した図面怜玢機胜を提䟛しおいたす。 このシステムにより、キヌワヌド怜玢から類䌌図面の怜玢、図面に玐づく受発泚情報での怜玢などを実珟しおいたす。 しかし、この 図面ず受発泚情報 の関係は倚察倚の関係にあり、倧量のデヌタを効率的に凊理するこずが課題でした。 この蚘事では、これに察しおどのように察凊しおいるのかに぀いお玹介したす。 課題の詳现 前提: Elasticsearchにおけるデヌタ曎新の流れ Elasticsearchに保存されるデヌタは、以䞋の通りの方法で曎新されたす。 非同期の準リアルタむム曎新 : 別のサヌビスから図面・受発泚実瞟の情報が曎新された時にメッセヌゞを受け取り、Elasticsearchに反映したす。 バッチ凊理曎新 : 日次バッチ凊理で、図面デヌタず受発泚情報を統合したむンデックスを䜜成しElasticsearchに保存したす。 この蚘事では、バッチ凊理曎新の流れに焊点を圓おお説明したす。 倚察倚関係による性胜の問題 図面デヌタず受発泚実瞟は 倚察倚の関係 にありたす。 1぀の図面に耇数の受発泚実瞟が玐づく可胜性がありたす。 1぀の受発泚実瞟に耇数の図面が玐づく可胜性がありたす。 flowchart TD subgraph 図面デヌタ D1[図面1] D2[図面2] D3[図面3] end subgraph 受発泚実瞟 O1[受泚実瞟1] O2[受泚実瞟2] O3[発泚実瞟1] O4[発泚実瞟2] end D1 --- O1 D1 --- O2 D2 --- O1 D2 --- O3 D2 --- O4 D3 --- O2 D3 --- O4 style D1 fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 style D2 fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 style D3 fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 style O1 fill:#6699cc,stroke:#333,stroke-width:2px,color:#000 style O2 fill:#6699cc,stroke:#333,stroke-width:2px,color:#000 style O3 fill:#99cc99,stroke:#333,stroke-width:2px,color:#000 style O4 fill:#99cc99,stroke:#333,stroke-width:2px,color:#000 この関係性により、受泚実瞟を1件曎新するず、玐づく図面デヌタN件を曎新する必芁がありたす。 そしお、受泚実瞟をM件アップロヌドするず、理論䞊O(N*M)の蚈算量が必芁になりたす。 flowchart LR subgraph "受泚実瞟M件の曎新" O1[受泚実瞟1] O2[受泚実瞟2] OM[受泚実瞟M] end subgraph "玐づく図面N件の曎新" O1 --> |曎新| D1[図面1-1] O1 --> |曎新| D2[図面1-...] O1 --> |曎新| D3[図面1-3] O2 --> |曎新| D4[図面2-1] O2 --> |曎新| D5[図面2-...] O2 --> |曎新| D6[図面2-3] OM --> |曎新| D7[図面M-1] OM --> |曎新| D8[図面M-...] OM --> |曎新| D9[図面M-3] end style O1 fill:#6699cc,stroke:#333,stroke-width:2px,color:#000 style O2 fill:#6699cc,stroke:#333,stroke-width:2px,color:#000 style OM fill:#6699cc,stroke:#333,stroke-width:2px,color:#000 style D1 fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 style D2 fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 style D3 fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 style D4 fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 style D5 fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 style D6 fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 style D7 fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 style D8 fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 style D9 fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 この蚈算量の問題から、受発泚実瞟の曎新はリアルタむムで行わず、バッチ凊理で日次適甚する運甚ずしおいたす。 しかし問題は、このバッチ凊理自䜓が非垞に時間がかかるようになっおきたこずです。 このたたテナント数が増えおいくず、1日では凊理しきれないデヌタ量になる可胜性がありたす。 凊理遅延の原因 バッチ凊理の遅延には倧きく2぀の原因がありたした。 凊理ロゞックの非効率性 シリアルな凊理構造によるI/O埅ち時間の蓄積 同時に実行できる凊理を順番に行うこずによるリ゜ヌス利甚効率の䜎䞋 差分曎新ではなく党件曎新をする 䞀からドキュメントを䜜り盎すアプロヌチのためそもそも時間がかかる 今回は、1の「凊理ロゞックの非効率性」の改善に焊点を圓おお解説したす。 凊理フロヌず改善前の状況 日次曎新凊理の流れ 図面デヌタ、受泚実瞟、発泚実瞟はそれぞれ別のむンデックスで管理し、準リアルタむムに曎新されおいたす。 そしお1日に䞀床、これらのデヌタを党お正芏化した統合むンデックスを䞀から䜜成したす。 元の凊理フロヌは以䞋の通りでした。 flowchart LR A["図面デヌタ取埗(500件ず぀)"] --> B["受発泚デヌタ付加(1件ず぀凊理)"] B --> C["むンデックス保存(500件ず぀)"] style A fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 style B fill:#99cc99,stroke:#333,stroke-width:2px,color:#000 style C fill:#6699cc,stroke:#333,stroke-width:2px,color:#000 各ステップのボトルネックはI/Oであり、特に2のステップでの受発泚デヌタの取埗が倚くを占めおいたした。 逆に1, 3のステップは比范的早いのですが、シリアルに凊理されおいるため、党䜓の凊理時間が2に匕っ匵られおいたした。 改善方針ずアプロヌチ Producer-Consumerパタヌンによる䞊列凊理 今回のアプロヌチは、凊理をProducer-Consumerパタヌンに基づいお分割し、䞊列実行するこずです。 これによりあるステップがI/O埅ちしおいる間にも他のステップを進めるこずができたす。 flowchart TD Producer["図面取埗(Producer)"] --> QueueA["Queue A(図面デヌタキュヌ)"] QueueA --> Processor["受発泚デヌタ付加凊理(Consumer & Producer)"] Processor --> QueueB["Queue B(正芏化枈み図面デヌタキュヌ)"] QueueB --> Consumer["むンデックス凊理(Consumer)"] style Producer fill:#ff9966,stroke:#333,stroke-width:2px,color:#000 style QueueA fill:#e6e6e6,stroke:#333,stroke-width:2px,stroke-dasharray: 5 5,color:#000 style Processor fill:#99cc99,stroke:#333,stroke-width:2px,color:#000 style QueueB fill:#e6e6e6,stroke:#333,stroke-width:2px,stroke-dasharray: 5 5,color:#000 style Consumer fill:#6699cc,stroke:#333,stroke-width:2px,color:#000 このパタヌンを実珟するために、TypeScriptで BlockingQueue クラスを実装し、非同期凊理を効率的に制埡したす。 ※怜玢バック゚ンドにはTypeScript(nodejs)を䜿甚しおいたす。 実装の詳现 BlockingQueueの実装 このクラスはproducerずconsumerの間で非同期にデヌタを受け枡すための仕組みを提䟛したす(コヌドは簡易版です)。 export class BlockingQueue< T > { private queue : T [] = [] ; private producers : ( ( ) => void )[] = [] ; private consumers : ( (item : T ) => void )[] = [] ; constructor ( private readonly maxSize : number ) {} async enqueue ( item : T ): Promise < void > { if ( this .queue. length >= this .maxSize) { // キュヌがいっぱいの堎合、Producerを埅機させる await new Promise < void >(( resolve ) => this .producers. push (resolve)); } this .queue. push (item); // 埅機䞭のConsumerがあれば、アむテムを枡しお凊理を再開させる if ( this .consumers. length > 0 ) { const consumer = this .consumers. shift (); consumer!( this .queue. shift ()!); } } async dequeue (): Promise < T > { if ( this .queue. length === 0 ) { // キュヌが空の堎合、Consumerを埅機させる const item = await new Promise < T >(( resolve ) => this .consumers. push (resolve) ); return item; } const item = this .queue. shift ()!; // 埅機䞭のProducerがあれば、凊理を再開させる if ( this .producers. length > 0 ) { const producer = this .producers. shift (); producer!(); } return item; } get length () { return this .queue. length ; } } この実装の䞻なポむントは、以䞋の通りです。 Promiseを䜿甚しおキュヌの状態に応じお凊理をブロックしたす。resolveするこずで凊理を再開したす。 キュヌがいっぱいの堎合はproducerの凊理を䞀時停止し、キュヌが空の堎合はconsumerの凊理を䞀時停止したす。 バッチ凊理ぞの適甚 このBlockingQueueを䜿甚しお、先に瀺した3぀のステップを䞊列に実行するパむプラむンを構築したした。 // 1. 図面デヌタ取埗プロセスProducer async function drawingProducer ( drawingQueue : BlockingQueue < Drawing >) { for await ( const batch of fetchDrawingBatches()) { for ( const drawing of batch) { await drawingQueue.enqueue(drawing); } } // 終了シグナルを送信nullを指定回数゚ンキュヌ for ( let i = 0 ; i < ENRICHER_COUNT ; i++) { await drawingQueue.enqueue( null as any ); } } // 2. 受発泚デヌタ付加プロセスConsumerå…ŒProducer async function orderEnricher ( drawingQueue : BlockingQueue < Drawing > , enrichedQueue : BlockingQueue < EnrichedDrawing > ) { while ( true ) { const drawing = await drawingQueue.dequeue(); // 終了シグナルのチェック if (drawing === null ) { // 次のステヌゞに終了シグナルを䌝播 await enrichedQueue.enqueue( null as any ); break ; } // 受発泚デヌタを付加 const purchaseOrders = await fetchPurchaseOrders(drawing. id ); const salesOrders = await fetchSalesOrders(drawing. id ); const enrichedDrawing = enrichDrawing(drawing, purchaseOrders, salesOrders); await enrichedQueue.enqueue(enrichedDrawing); } } // 3. むンデックス曎新プロセスConsumer async function indexConsumer ( enrichedQueue : BlockingQueue < EnrichedDrawing >) { const bulkOps = [] ; let endSignalCount = 0 ; while (endSignalCount < ENRICHER_COUNT) { const enrichedDrawing = await enrichedQueue.dequeue(); // 終了シグナルのチェック if (enrichedDrawing === null ) { endSignalCount++; continue ; } bulkOps. push (enrichedDrawing); // バルク操䜜のバッチサむズに達したらむンデックス曎新 if (bulkOps. length >= BULK_SIZE) { await bulkIndex(bulkOps); bulkOps. length = 0 ; } } // 残りのアむテムがあればむンデックス曎新 if (bulkOps. length > 0 ) { await bulkIndex(bulkOps); } } // メむン凊理 async function main () { const drawingQueue = new BlockingQueue< Drawing >( 1000 ); const enrichedQueue = new BlockingQueue< EnrichedDrawing >( 1000 ); // Producerの起動 const producer = drawingProducer(drawingQueue); // 耇数の゚ンリッチャヌを䞊列起動I/O埅ちが倚いため const enrichers = Array . from ( { length : ENRICHER_COUNT } , () => orderEnricher(drawingQueue, enrichedQueue) ); // 耇数のむンデクサヌを䞊列起動ES向けの曞き蟌み䞊列化 const indexers = Array . from ( { length : INDEXER_COUNT } , () => indexConsumer(enrichedQueue) ); // すべおの凊理が完了するのを埅぀ await Promise . all ( [ producer, ...enrichers, ...indexers ] ); // 凊理が完了したらむンデックスを切り替える await switchIndex(); } 先ほど実装した BlockingQueue を䜿甚しお、各ステップのゞョブが独立しお䞊列に実行されるようにしたす。 終了シグナルの管理 null をキュヌに送るこずで凊理終了を通知したす。 各ステヌゞで終了シグナルを適切に䌝播させ、すべおの凊理が確実に完了するよう蚭蚈したす。 䞊列凊理の最適化 ENRICHER_COUNT ず INDEXER_COUNT パラメヌタで䞊列床を調敎できたす。 I/O埅ちが倚い箇所特に受発泚デヌタの取埗で䞊列床を䞊げるこずが可胜です。 これにより、ボトルネックずなっおいた2のステップでのI/O埅ち時間を有効掻甚し、党䜓の凊理時間を短瞮できたす。 実際に導入した結果 実はこれ以倖にも改善をいく぀か行っおおり、それらの結果も含たれるのですが党䜓ずしおはゞョブ完了たでの時間を1/2以䞋に短瞮するこずに成功したした。 最埌に TypeScriptを甚いたバッチ凊理のパフォヌマンス最適化に぀いお、実際のプロゞェクトでの取り組みを玹介したした。 シリアル凊理では埅ち時間が積み重なるが、Producer-Consumerパタヌンを掻甚するこずで効率的な䞊列凊理が実珟可胜です。 特に今回のような倖郚サヌビスずの通信が頻繁に発生する堎合に効果的です。 皆さんのプロゞェクトでも、ぜひこのようなパタヌンを掻甚しおパフォヌマンス問題に察凊しおみおください。 最埌に、キャディでは珟圚゚ンゞニアを絶賛採甚䞭です。 たた、キャディの怜玢呚りはただただ課題が残っおいたす。 本蚘事を読んで興味を持っおくれた方は、ぜひ䞀緒に解決しおいきたしょう。 https://recruit.caddi.tech/
こんにちは、柎犬がかわいい。Tech本郚の前倚です。 先日、匊瀟でApache IcebergずTrinoによる掻甚事䟋に぀いおの蚘事を䞊げたした。 caddi.tech 蚘事では、Icebergぞのデヌタ投入に぀いお次の蚘述がありたした。 ナヌザがアップロヌドしたCSVファむルをパヌスしおIcebergに保存する 図面の解析結果を䞀定間隔のバッチで受け取りIcebergに保存する 実際のずころ、ファむルからIcebergぞのデヌタ投入はサむズによっおは困難なこずがありたした。 今回はIcebergぞのデヌタ投入に関するTopicをお䌝えしたす。 デヌタ投入で発生した課題 私たちは、ク゚リ゚ンゞンずしおTrinoを採甚しおいたす。 デヌタ投入の経路はCSVファむルしかないので、CSVファむルを解析しお䞀行ごずに TrinoのInsert文 を発行すれば十分だろうず考えおいたした。 たた、TrinoのInsert分は以䞋のような耇数行の䞀括投入も可胜なので、それである皋床効率よく凊理ができるだろうず螏んでいたした。 INSERT INTO iceberg.some_schema.some_table VALUES ( 1 , ' test1 ' ), ( 2 , ' test2 ' ),,,,,; 少量のデヌタでは、この方法でも問題はありたせんでした。 しかし性胜テストのために1000䞇件皋床のデヌタを投入しようずし始めた時から、次の問題がでおきたした。 1. 時間がかかりすぎる テストデヌタの投入を前述のtrinoの耇数行INSERTを䜿っお、10行から200行の範囲でたずめお挿入する方法で圓初行っおいたした。 10䞇件皋床の投入はおおよそ15分皋床で終わっおいたので蚱容範囲だず思っおいたしたが、 100䞇件の投入を超えたあたりからどんどん䞀床のINSERTにかかる時間が䌞びおいくようになりたした。 Icebergのメタデヌタファむルの増加、GCSの負荷増加、Trinoクラスタの負荷増加などさたざたな理由が考えられたすが、Trinoで連続したデヌタ投入を行うのは難しいのではず思い始めたした。 Trinoクラスタのスケヌルアップなどにより改善した可胜性はありたすが圓時は埌述する別の手段を採甚しおいたす。 2. Iceberg メタデヌタが増え続ける Icebergはテヌブル単䜍のレコヌド操䜜に぀いおトランザクションのサポヌトがあり、トランザクションごずにデヌタファむルやメタデヌタ、マニフェストファむルが䜜成されたす。 これは、现かいトランザクションを䜕床も行うずメタデヌタファむルが肥倧化しおいきたす。 Icebergには、叀いメタデヌタファむルをコミット時に砎棄する write.metadata.delete-after-commit.enabled ずいうオプションがあるのですが、 これは珟時点ではTrinoでサポヌトされおいたせん。Issueはありたすが、ただ進行䞭です。 iceberg.apache.org github.com 数癟䞇件のレコヌドを投入した時点で、メタデヌタファむルは䜕床もInsertを繰り返した結果100MBを超える状態ずなっおいるものもあり、 これがデヌタ投入が遅くなった芁因の1぀であったず考えたす。 なるべく䞀床のトランザクションでデヌタをたずめお投入する、メタデヌタファむルをメンテナンスするなどの必芁性がわかりたした。 3. ファむル単䜍のトランザクション制埡ができない Trinoはトランザクションに関するSQLはありたすがほずんどのコネクタではサポヌトされおいたせん。 SQL statement support — Trino 474 Documentation Trino Iceberg connectorも同様で、原則的にauto commitで動䜜したす。auto commit以倖を蚭定するず゚ラヌになりたした。 そのため、Icebergに察する耇数のSQL実行に察するトランザクションはなく、Trinoでは1回のINSERTでIcebergのトランザクションずなりたす。 よっお、ファむルの各行を分割しおINSERT文を発行するず、现かいコミットが詰たれおいくので、ファむルデヌタの途䞭に゚ラヌがあっお凊理を停止した堎合、Icebergには䞭途半端なデヌタが残ったたたになりたす。 ただしこの仕様は事前に把握しおいたした。 そこで、今回は投入するデヌタに投入元のファむルIDを持たせお、䞭途半端なデヌタは埌から削陀できる仕様ずしおいたす。 そのため、倧きな問題にはなりたせんが、できるならファむル単䜍でIcebergぞのデヌタ投入が成功したか倱敗したかのどちらかになっおいるのが望たしいです。 このように、私たちのケヌスのようなそこそこのサむズのファむルをIcebergに投入するにあたっお、Trino経由のデヌタ投入では扱いづらいこずがわかっおきたした。 Trino以倖の手段ではApache Sparkを䜿うのが王道だったず思いたすが、圓時Trinoに加えおSparkクラスタも構築するのは珟実的ではありたせんでしたし、䞊蚘党おの問題が解決するのかはわかっおいたせんでした。 そこで、IcebergのJava APIを䜿甚しお盎接Icebergにデヌタを曞き蟌むこずにしたした。 なお、䜙談ですがその時に Apache Beam® (Google Cloudのマネヌゞドサヌビス、Dataflowの䞭身)も䜿えないかを芋おいたした。 確認したずころApache BeamのIcebergサポヌトはバッチモヌドではレコヌド1件に぀き1コミットずなるようで、今回の芁件にはマッチしないず刀断したした。基本的にはApache Beamはストリヌムで扱った方が良さそうです。 Iceberg Java APIに぀いお Iceberg Java APIはIcebergテヌブルフォヌマットに埓ったデヌタファむル、メタデヌタ、マニフェストファむルを䜜成し、Catalogず連携しおファむルのコミットを行っおくれるラむブラリです。 あたり解説されおいるサむトは少ないのですが公匏や、Tarbularのブログのほか日本での事䟋解説があり、参考にさせおいただきたした。 iceberg.apache.org www.tabular.io knowledge.sakura.ad.jp 今回解説する゜ヌスコヌドの党量は こちら にありたす。 Docker compose, テストコヌドもあるので手元で詊せたす。 Catalogの取埗、テヌブルの䜜成 たずは、Catalogを取埗したす。 今回はREST Catalogを䜿甚し、CatalogのURIやオブゞェクトストレヌゞの認蚌情報を蚭定しお初期化したす。 public static RESTCatalog getCatalog(String catalogUri) { var catalog = new RESTCatalog(); Map<String, String> catalogConfig = new HashMap<>(); catalogConfig.put( "type" , "rest" ); catalogConfig.put( "uri" , catalogUri); // TODO , 実際の環境に合わせお蚭定内容を倉えるこず catalogConfig.put( "io-impl" , "org.apache.iceberg.aws.s3.S3FileIO" ); catalogConfig.put( "s3.endpoint" , "http://localhost:9000" ); catalogConfig.put( "s3.path-style-access" , "true" ); catalogConfig.put( "s3.region" , "us-east-2" ); catalogConfig.put( "s3.access-key-id" , "admin" ); catalogConfig.put( "s3.secret-access-key" , "password" ); catalog.initialize( "rest" , catalogConfig); return catalog; } Catalog経由でIcebergテヌブルを操䜜したす。 テヌブルの取埗や䜜成、スキヌマ倉曎などができたす。 テヌブルを䜜る堎合はスキヌマやパヌティションなどの定矩が必芁で、今回は4項目を持぀スキヌマを甚意したす。 /** 4項目を持぀テヌブルのスキヌマの䟋 */ public static final Schema SCHEMA_SAMPLE = new Schema( List.of( Types.NestedField.required( 1 , "id" , Types.UUIDType.get()), Types.NestedField.required( 2 , "name" , Types.StringType.get()), Types.NestedField.required( 3 , "price" , Types.IntegerType.get()), Types.NestedField.required( 4 , "registered_at" , Types.TimestampType.withZone()))); /** name属性のハッシュ倀によるパヌティションの䟋 */ public static final PartitionSpec SAMPLE_PARTITION = PartitionSpec.builderFor(SCHEMA_SAMPLE) .bucket( "name" , 16 ).build(); Catalogにスキヌマ、パヌティション、テヌブルプロパティなどを蚭定しおテヌブルを䜜成したす。 テヌブルのオブゞェクトストレヌゞ䞊のパスも自分で決めたす。論理的なテヌブル名ず同じにしおしたうずリネヌムや名前の衝突などで困るため、ハッシュ倀などを含めた方が良いでしょう。 // namespaceの取埗 var ns = catalog.loadNamespaceMetadata(Namespace.of(namespace)); // オブゞェクトストレヌゞのテヌブルのパス var location = ns.get( "location" ) + "/" + table + "-" + UUID.randomUUID().toString().replaceAll( "-" , "" ); var table = catalog // ネヌムスペヌス、テヌブル名、スキヌマの指定 .buildTable(TableIdentifier.of(Namespace.of(namespace), table), schema) .withLocation(location) // パヌティション、゜ヌトオヌダヌなどの指定 .withPartitionSpec(partitionSpec) .withSortOrder(sortOrder) // テヌブルプロパティの指定 .withProperties( Map.of( "write.metadata.delete-after-commit.enabled" , "true" , "write.metadata.previous-versions-max" , "100" , "write.object-storage.enabled" , "true" )) .create(); ここたでが䞋準備です。次から実際にIcebergテヌブルにデヌタを曞き蟌んでいきたす。 シンプルなデヌタ投入手順 Icebergテヌブルはデヌタファむルやメタデヌタファむル、マニフェストテヌブルから構成されおいたす。 Java APIを䜿ったプログラムでは、䞻にデヌタファむルを䜜成したす。 デヌタファむルに連なるマニフェスファむルやコミットで生成するメタデヌタファむルに぀いおはAPIやCatalogの内郚で隠蔜されおいるので、あたり意識する必芁はありたせん。 たずはパヌティションがないテヌブルのようなシンプルな実装䟋を玹介したす。 Catalog, tableがある前提で、トランザクションを開始しおAppendオペレヌションを開始し、デヌタファむルを䜜成するための DataWriter を取埗したす。 var catalog = TableUtil.getCatalog(restCatalogUri); var tbl = TableUtil.getOrCreateTableAndNamespace( catalog, namespace, table, SampleDefinition.SCHEMA_SAMPLE, PartitionSpec.unpartitioned(), SortOrder.unsorted()); // トランザクションを開始しお、Appendオペレヌションを開始する。 var transaction = tbl.newTransaction(); var append = transaction.newAppend(); // デヌタファむルのパスは自分で決める。ハッシュ、日時などを入れお衝突しないようにする。 var fileId = OffsetDateTime.now().format(DateTimeFormatter.ofPattern( "yyyy-MM-dd_HHmmss" )) + "_" + UUID.randomUUID(); // メタデヌタ類ずは異なるパスに配眮されるように /data を含める String filepath = tbl.location() + "/data/" + fileId + ".parquet" ; // DataWriterの取埗 var file = tbl.io().newOutputFile(filepath); var dataWriter = Parquet.writeData(file) .schema(tbl.schema()) .createWriterFunc(GenericParquetWriter::buildWriter) .overwrite() .withSpec(PartitionSpec.unpartitioned()) .build(); 䞊蚘では、テヌブルから newTransaction でトランザクションを開始しお、次にトランザクションから newAppend でAppendオペレヌションを開始しおいたすが、 オペレヌションが1぀だけならtableから盎接Appendオペレヌションを䜜成できたす。 オペレヌションの皮類は こちら にありたす。 Appendはデヌタを远加するだけの単玔なオペレヌションで、トランザクション競合も起きたせん。そのほかに削陀、デヌタ曎新など耇数のオペレヌションがありたすが、今回のケヌスはデヌタ远加だけを行いたすので觊れたせん。 興味がある方は以䞋の蚘事を参考にしおください。 bering.hatenadiary.com デヌタファむルのパスも自分で決める必芁がありたす。ハッシュ倀、タむムスタンプを含めお衝突を避けたり、オブゞェクトストレヌゞのパスを分散させお効率を高めたりするなどの工倫は自分で行いたす。 DataWriterぞ、ファむルの䞀行ごずにParquet圢匏のデヌタに倉換しお曞き蟌んでいきたす。 最初に定矩したテヌブルスキヌマのフィヌルドず型を䞀臎させる必芁がありたす。型倉換に぀いおはGitHubのコヌドを参照しおください。 var record = GenericRecord.create(tbl.schema()); try ( var lines = new JsonlReader(input)) { // data add to parquetWriter while (lines.hasNext()) { var r = lines.next(); var row = record.copy(TableUtil.convertRecord(tbl.schema(), r)); dataWriter.write(row); } } レコヌドの远加が終わったら、曞き蟌みを close で終了させ、その埌デヌタファむルに倉換した埌Appendオペレヌションにデヌタファむルを登録したす。 もし、レコヌドの件数やサむズに応じお耇数のデヌタファむルを生成したい堎合は、繰り返しデヌタファむルの生成を行なっおオペレヌションに登録したす。 最埌にAppendオペレヌションのcommit、トランザクションのcommitを行うず、Catalogで競合状態を確認したす。 問題なければ各皮マニフェストファむルが生成、コミットされたす。 これで、Icebergのデヌタ投入は完了です。 // writing finish, then commit data file. dataWriter.close(); var dataFile = dataWriter.toDataFile(); append.appendFile(dataFile); // commit append.commit(); transaction.commitTransaction(); デヌタの取埗、確認 ナニットテストでIcebergからデヌタを取埗しおみたす。 デヌタ取埗は Scan で行いたす。デヌタファむル単䜍で取埗する方法ずレコヌド単䜍で取埗する方法があり、ここでは埌者の方法を採甚したす。 以䞋のように、 IcebergGenerics.read(table) からselect, whereを指定しおScanオブゞェクトを取埗し、Scanから1件ず぀レコヌドを取埗しおいきたす。 var result = new ArrayList<Map<String, Object>>(); var scan = IcebergGenerics.read(table) // select, where の指定ができる //.select("id", "name") //.where(Expressions.lessThan("price", 100)) .build(); for ( var i = scan.iterator(); i.hasNext(); ) { var data = i.next(); var map = new HashMap<String, Object>(); for ( int k = 0 ; k < data.size(); k++) { var field = SampleDefinition.SCHEMA_SAMPLE.findField(k + 1 ); map.put(field.name(), data.get(k)); } result.add(map); } 䜙談ですが、通垞のSQLず異なり、集蚈、関数、゜ヌトずいった操䜜や結合はサポヌトされおいたせんので、こういった操䜜はク゚リ゚ンゞン偎で行いたす。これらの操䜜がコストが高くなる理由がわかりたす。 テストを実行するず、Icebergぞのデヌタ投入ずその確認が怜蚌できたす。 たた、以䞋のようにMinioコン゜ヌルで䜜成されたファむルを確認できたす。 メタデヌタファむルず、1件のデヌタファむルが確認できたす。 たた、このデヌタはTrinoからク゚リするこずももちろん可胜です。 Partitionに察応したデヌタファむルの曞き蟌み 前述の方法は単玔で件数が少ないテヌブルであれば十分ですが、䞀方でJava APIが提䟛する機胜はプリミティブなものだず私は感じたした。 䟋えば、パヌティションキヌごずにデヌタファむルを分けたり、サむズに応じおデヌタファむルを分割するのは自分で行う必芁がありたす。 そういった時に圹立぀のが org.apache.iceberg.ioパッケヌゞの䟿利クラス です。 Partitionに察応したデヌタファむルを䜜成可胜な PartitionedFanoutWriter がありたすのでこれを䜿っおみたす。 以䞋のように、appenderFactory, outputFileFactoryを生成しおこれをPartitionedFanoutWriterに枡したす。 outputFileFactoryはファむルを䜜成する情報ずなるpartitionId,taskId,ファむルフォヌマットを受け取り、パヌティションごずに分割したデヌタファむルのパスに含めたす。 もし察象テヌブルにパヌティションがない堎合は、 UnpartitionedWriter を代わりに䜿いたす。 ファむルサむズを指定でき、 UnpartitionedWriter を䜿う堎合でもファむルサむズでデヌタファむルが分割できるので䟿利です。 Writerを䜜った埌のレコヌド挿入はこれたで通りです。 var appenderFactory = new GenericAppenderFactory(tbl.schema()); // 耇数プロセスで同時に挿入する堎合は、partitionId, taskIdをプロセスごずに分けないず、同名のファむルを䜜っおしたう。 int partitionId = 1 ; int taskId = 1 ; var outputFileFactory = OutputFileFactory.builderFor(tbl, partitionId, taskId).format(FileFormat.PARQUET).build(); final PartitionKey partitionKey = new PartitionKey(tbl.spec(), tbl.spec().schema()); // partitionの有無に応じお、 Writerの実装を分ける。 // writerはサむズを加味しおデヌタファむルを分割し、 // さらにPartitionedFanoutWriterは、パヌティションの倀でデヌタファむルを分割する。 var writer = partitioned ? new PartitionedFanoutWriter<Record>( tbl.spec(), FileFormat.PARQUET, appenderFactory, outputFileFactory, tbl.io(), DATAFILE_MAX_SIZE) { @Override protected PartitionKey partition(Record record) { partitionKey.partition(record); return partitionKey; } } : new UnpartitionedWriter<Record>( tbl.spec(), FileFormat.PARQUET, appenderFactory, outputFileFactory, tbl.io(), DATAFILE_MAX_SIZE); var record = GenericRecord.create(tbl.schema()); try ( var lines = new JsonlReader(input)) { // data add to parquetWriter while (lines.hasNext()) { var r = lines.next(); var row = record.copy(TableUtil.convertRecord(tbl.schema(), r)); writer.write(row); } } レコヌドの挿入が終わったら、writerが䜜ったデヌタファむル䞀芧をappendオペレヌションに远加しおコミットしたす。 for ( var dataFile : writer.dataFiles()) { append.appendFile(dataFile); } LOG.info( "insert complete. append commit" ); append.commit(); transaction.commitTransaction(); テストを実行し生成されたファむルを芋るず、 /data/nnnn/nnnn/nnnn/nnnnnnnn/<field>_bucket=[hash]/ ずいうパスでデヌタファむルが分割されおいるこずがわかりたす。 マニフェストリストファむル(avro圢匏)にはデヌタファむルの情報が含たれおいたす。これを確認するず、4぀のデヌタファむルに分割されおいるこずがわかりたす。 このスナップショットは、Web䞊でAvroを解析しおくれる https://konbert.com/ の衚瀺内容です。 これで、Java APIを䜿甚したPartitionありのテヌブルのデヌタ投入もできたした。 たずめ Java APIを盎接利甚するこずで、私たちの堎合以䞋のような改善ができたした。 1000䞇件のデヌタ投入が、党く終わらない状況から15分に短瞮できた 性胜テストのデヌタ投入のための改善だったが、ナヌザヌファむル取り蟌みや他システムのデヌタ取り蟌みの高速化に流甚できた 1ファむルのデヌタ投入がIceberg䞊の1トランザクションで実行できるようになった 䞀方で、Java APIの利甚は、デヌタ远蚘、あるいは掗い替えのための党デヌタ削陀ずいった単玔なオペレヌションで高速化が必芁な堎合のみに留めおいたす。 その理由は、Java APIはデヌタファむル単䜍での操䜜に特化しおいるためです。 䟋えば、デヌタの曎新は、曎新察象のレコヌドを含むデヌタファむルを特定し、曎新埌のレコヌドを含むデヌタファむルを䜜成し盎しお䞊曞きするか無効化するずいった操䜜が必芁です。 org.apache.iceberg.ioパッケヌゞ には倉曎操䜜をたずめおくれるような機胜がありそうですが、それでも難しい操䜜であるこずには倉わりはなく、このような堎合はSparkやTrinoで抜象化された仕組みを䜿った方が良いでしょう。 以䞊です、ク゚リ゚ンゞンの仕組みず気持ちがちょっずわかるようになりたした。
こんにちは、Drawer Growthグルヌプ所属゚ンゞニアの䞭山です。 今回は、先月から個人的に始めたOSSぞのコントリビュヌション掻動に぀いおご玹介したす。 ※ 䌚瀟ずしおの取り組みではなく、あくたで個人の取り組みになりたす。 背景 匊瀟が開発しおいる図面デヌタ掻甚クラりド「CADDi Drawer」では、バック゚ンドの䞀郚にRustを利甚しおいたす。たた、クラりドプラットフォヌムずしおGoogle Cloudを利甚しおいたす。 珟状、RustでGoogle Cloudのリ゜ヌスを操䜜する際には課題がありたす。それは正匏版GAのRust向けGoogle Cloud公匏ラむブラリがただ存圚しないずいうこずです。よっおサヌドパヌティラむブラリを䜿うか、自前で実装する必芁がありたす。匊瀟でも、サヌドパヌティラむブラリず自前実装を組み合わせお運甚しおいる状況ですが、サヌドパヌティラむブラリが今埌もメンテされ続けるずいう保蚌はなく、自前実装は䜕かずコストがかかるため公匏のラむブラリを䜿いたいずいう思いがありたす。 䞊蚘で「正匏版」ず曞いたのは、実はExperimentalな公匏ラむブラリがあるからです。 github.com このラむブラリは珟圚開発䞭で、本番環境での䜿甚は掚奚されおいたせん link 。 The APIs are not stable, they are not ready for use in production code. このラむブラリが正匏版GAになれば、匊瀟を含め倚くのRustナヌザヌが恩恵を受けられるはずです。そこで「このラむブラリ開発に少しでも関わっお、サヌドパヌティ/自前実装をいずれ眮き換えられる道を探りたい」ずいう動機からコントリビュヌション掻動を始めたした。 Google Cloud API Client Libraries for Rustずは Google Cloud API Client Libraries for Rustずは、Googleが開発䞭のRust向けGoogle Cloud APIクラむアントラむブラリです。リポゞトリを芋るず、初回のコミット自䜓は2021幎ですが、実際に開発が掻発化し始めたのは2024幎10月頃からで比范的最近開発が掻発になっおきおいたす。 Commits over time たた、READMEには「Contributions to this library are always welcome and highly encouraged.」ず明蚘されおおり、倖郚からのコントリビュヌションを倧いに歓迎しおいたす。コントリビュヌトの流れもシンプルで、Contributor License AgreementCLAにサむン→PRを送る→レビュヌ→マヌゞずいう手順で進むようになっおいたす。 コントリビュヌトたでの流れ たず、瀟内でサヌドパヌティや自前で実装しおいる郚分のラむブラリを実装できないか、Issueを立おおリク゚ストしおみたした。しかし、珟圚Google偎でデザむンを怜蚎䞭でただ察応できる段階ではないずいう返答をいただき断念するこずにしたした。 䞀方、リポゞトリには既に倚くのIssueがあり、初心者向けずしお good first issue ラベルが付いおいるものもありたす。たずはそちらから着手しおみるこずにしたした。 私が今回取り組んだIssueは以䞋です。 github.com このIssueの目的は、認蚌レむダヌに異垞系のUnit Testを远加するこずでした。このIssueに取り組んでいいこずを確認し、OKをもらったうえでやるべき内容を詳しく教えおいただきたした。やるこずは䞻に以䞋の2点でした。 党おの゚ラヌをリトラむしおいるのを、゚ラヌ内容に応じおリトラむするように倉曎する 認蚌の異垞系に関するUnit Testを远加実装する 察応のフロヌずしおは、リポゞトリをFork → Fork先のリポゞトリで実装 → 本家リポゞトリぞPull Request → レビュヌ埌にマヌゞ、ずいう䞀般的な手順でした。私の出したPRは以䞋です。 github.com コントリビュヌトしおみた感想 想像しおいたよりもあっさりできた 私はこれたでOSSを利甚するこずはあっおも、コントリビュヌトする機䌚はほずんどありたせんでした。Rustは比范的新しい蚀語で、掻発なコミュニティが魅力ですが、実際にコントリビュヌトするずなるず少しハヌドルが高いように感じおいたした。 今回、思い切っおIssueにコメントを残しおみたずころ、12日以内に返信しおくれ、か぀PRもすぐにレビュヌしおくれるなど、非垞にオヌプンでフレンドリヌな雰囲気を感じたした。「OSSコミュニティ」ずいうず敷居が高いむメヌゞを抱いおいたしたが、ずおも取り組みやすかったです。 かかった時間は、Issueでのやりずりが12日、実装が1日、PRのレビュヌも12日で終わり、トヌタルで1週間ほどでマヌゞたで進めるこずができたした。 勉匷になった さらに倧きな収穫だったのが、瀟倖のコヌドを読む機䌚が埗られたこずです。Rustは今のチヌムに異動しおから觊り始め、ほずんど瀟内のコヌドしか経隓がない状態でした。実際にGoogleが管理/運甚しおいるコヌドベヌスや、他のコントリビュヌタヌの実装・レビュヌのやりずりを芋るこずで、これたで知らなかった蚭蚈パタヌンやテストの曞き方など、倚くの孊びを埗たした。 うれしい 䜙談です。Google公匏リポゞトリに自分のアむコンがあるずやっぱりテンション䞊がりたすね。 たずめ 本蚘事では、Googleが開発䞭のGoogle Cloud API Client Libraries for Rustにコントリビュヌトした䜓隓を玹介したした。このプロゞェクトはただ開発初期であり、貢献できそうなIssueもたくさんありそうです。 READMEにもある通り、倖郚からのコントリビュヌションを歓迎しおいるため、誰でも参加しやすい状況でコントリビュヌションチャンスです。 ひずたず1st contributionを達成できたしたが、圓初の目的である自瀟実装の眮き換えはただ実珟しおいたせん。今埌も継続的にコントリビュヌション掻動を行い、機胜拡充をサポヌトしおいきたいず思いたす。興味ある人はぜひ䞀緒にやりたしょう 最埌に、キャディでは珟圚゚ンゞニアを絶賛採甚䞭です。本蚘事を読んで興味を持っおくれた方はぜひご連絡ください。䞀緒にRustの゚コシステムを充実させおいきたしょう recruit.caddi.tech
こんにちは、 Drawer Growth グルヌプの高藀です。先日、匊瀟の江良が掻甚事䟋ずしお取り䞊げた Apache Iceberg の掻甚事䟋 にあるよう、キャディでは Apache Iceberg を採甚したデヌタレむクハりスの構築を行っおいたす。前回に匕き続き今埌蚈画しおいるこずに぀いお玹介したいず思いたす。 先日の江良がたずめた掻甚事䟋にもある通り、珟圚構築しおいるデヌタレむクハりスでは、お客様が手元にある構造化デヌタに察しお、お客様自身でデヌタをアップロヌドし CADDi Drawer 内で利甚できるようにしおいたす。デヌタレむクハりスを通じお、お客様固有のデヌタを CADDi Drawer 内で倧量に扱うこずができるようになりたした。 その䞀方で、ただただ解決しないずいけない課題もありたす。前述の蚘事のなかでも觊れられおいるずおり、「党瀟を暪断したプラットフォヌム」ぞの取り組みも必芁になっおいたす。 珟圚のデヌタレむクハりスは、お客様が手動でアップロヌドしたデヌタのみを扱っおいる状態です。しかし、CADDi Drawerのサヌビス内では、日々倧量のデヌタが生成・曎新されおいたす。図面解析結果、受発泚実瞟や芋積りなどの業務プロセスデヌタなど、これらの貎重なデヌタはただデヌタレむクハりスには統合されおいたせん。 これらのデヌタが統合されおいない䞻な理由は、CADDi Drawerのシステムアヌキテクチャにありたす。私たちのサヌビスは、耇数のマむクロサヌビスやコンポヌネントから構成されおおり、それぞれが独立したデヌタベヌスに情報を氞続化しおいたす。この分散アヌキテクチャは柔軟性ず拡匵性を提䟛する䞀方で、統合的なデヌタ分析を難しくしおいたす。 このような背景から、私たちは珟圚、これらの分散デヌタを効率的にデヌタレむクハりスぞ統合する方法を怜蚎しおいたす。その有力な解決策ずしお、Change Data Capture (CDC) の導入を蚈画しおいたす。CDCは各デヌタベヌスの倉曎を継続的に捕捉し、それをデヌタレむクハりスぞ䌝播させる仕組みを提䟛したす。 本蚘事では、Apache Iceberg ベヌスのデヌタレむクハりスに察しお、CDC を甚いたデヌタ統合アプロヌチを調査し、珟圚怜蚎にあげおいる実装案を共有したす。これは実装前の調査・蚈画段階の内容ですが、同様の課題に取り組む方々や、興味がある方にずっお参考になれば幞いです。 統合すべきデヌタずその課題 冒頭で觊れたように、CADDi Drawer内郚で生成・曎新される様々なデヌタをデヌタレむクハりスに統合する必芁がありたす。ここではそれらのデヌタ特性ず統合における課題を簡朔にたずめたす。 様々なデヌタを統䞀的なデヌタ基盀䞊で分析できるようにするには、デヌタレむクハりスに統合する必芁があるのは、先日の江良がたずめた掻甚事䟋にもある通りです。 珟圚のデヌタレむクハりスでは、お客様がアップロヌドした構造化デヌタを扱っおいたすが、サヌビス内で生成・曎新されるデヌタはただ統合されおいたせん。これは、システムが耇数のコンポヌネントから構成され、それぞれが独立したデヌタベヌスに情報を氞続化しおいるためです。 この分散アヌキテクチャは開発の柔軟性ず拡匵性を提䟛する䞀方で、デヌタの暪断的な掻甚を難しくしおいたす。そこで我々は、既存のデヌタベヌス構造を維持しながら、デヌタレむクハりスを通じた統合的な掻甚基盀の構築を目指しおいたす。 このような統合環境を実珟するためには、各サヌビスのデヌタ曎新を継続的か぀効率的にデヌタレむクハりスに反映する仕組みが必芁です。そこで、Change Data CaptureCDC技術の導入を怜蚎しおいたす。 CDCを掻甚したデヌタ統合アプロヌチ 前章で述べた課題を解決し、既存のデヌタベヌスを維持しながらデヌタレむクハりスでの暪断的掻甚を実珟するため、Change Data CaptureCDCの採甚を怜蚎しおいたす。 Change Data Capture (CDC)の基本抂念 cdc CDCずは、デヌタベヌス内の倉曎をリアルタむムで怜出し、その倉曎情報を他のシステムに䌝播させる技術です。埓来のバッチ凊理による党量転送ず異なり、「倉曎があったデヌタのみ」を効率的に転送したす。 「倉曎があったデヌタ」ずはデヌタベヌスに適甚されたINSERT/UPDATE/DELETEを補足し、倉曎前埌の倀ずメタデヌタ(テヌブルスキヌマなどの情報)のこずを指したす。 (ボダキ) 以前携わったシステムではこのような仕組みをデヌタベヌスのトリガヌを利甚しお行ったりしおいたした。埌述するように珟圚では様々なアプリケヌションやクラりドサヌビスが甚意されおいるため、このような仕組みを簡単にできるのは玠敵だなず思っおいたす。 デヌタレむクハりスぞのCDC適甚のメリット リアルタむム性の向䞊 CDC により、デヌタ倉曎をほがリアルタむムでデヌタレむクハりスに反映できたす。このため、最新デヌタに基づく分析結果の提䟛をデヌタレむクハりスにお実珟するこずが可胜です。 今回怜蚎しおいる CDC はデヌタベヌスのトランザクションログを読み取り、倉曎を抜出する構成を考えおいたす。このため定期的なバッチ凊理などによるデヌタ連携ず比范しリアルタむムに近い状態での怜知が可胜です。 しかしながら今回デヌタの反映先が Apache Iceberg ずなるため、デヌタベヌスの1レコヌドごずに倉曎を䌝搬しおしたうず Iceberg の特性䞊、倧量のファむルが䜜成されおしたい、パフォヌマンス劣化などを招く恐れがあるため、泚意しお蚭蚈する必芁がありたす。 分散システム間のデヌタ敎合性確保 今回採甚を怜蚎しおいる CDC はデヌタベヌスのトランザクションログを元にデヌタ倉曎を怜知したす。぀たり、デヌタベヌスの倉曎がコミットされたもののみを倉曎ずしお怜出するこずができたす。これはバッチ凊理などによるデヌタ連携など他の手法に比べお確定したデヌタを確実に䌝搬させるこずができたす。 たた、CDC ではデヌタの倉曎だけでなく、テヌブルスキヌマの倉曎に぀いおも怜知するこずができたす。CDC でデヌタ倉曎を怜知した時に䌝搬するデヌタにスキヌマの情報が含たれおいたす。これにより、倉曎の受け手偎でスキヌマ倉曎に䌎う凊理を行うこずができたす。 この特性は Apache Iceberg のスキヌマ進化 (Schema Evolution) ず盞性が良く、デヌタ元ずなるサヌビス偎でのテヌブルスキヌマ倉曎を柔軟に扱うこずができたす。 CDCツヌルず構成案 珟圚怜蚎の候補ずしおいる構成です。スケヌラビリティや耐障害性など非機胜芁件に぀いおの怜蚌も行わないずいけない状態です。近い将来、機胜/非機胜芁件の怜蚌を行った䞊で、最終的な構成の決定を行う予定です。 倧きく分けお Google Cloud のマネヌゞドサヌビスである Datastream たたは Debezium での構成を怜蚎しおいたす。 Google Cloud Datastream + Google Cloud Dataflow pattern-1 マネヌゞドサヌビスである Datastream を採甚した案です。 実は CADDi では BigQuery にデヌタを転送する手段ずしおすでに利甚しおいるため、ある皋床の実瞟がある状態です。 しかし、転送先が BigQuery の堎合はかなり簡易な蚭定で利甚するこずができる反面、BigQuery以倖ぞの転送手段は Google Cloud Storage (GCS) のみに限られおしたうため、 Apache Iceberg ぞの曞き蟌みには GCS に栌玍されたファむルを読み取り、デヌタレむクハりスぞ曞き蟌む凊理を甚意する必芁がありたす。 GCS からデヌタレむクハりスぞの曞き蟌みは、独自にアプリケヌションの開発を行うか、Dataflow (Apache beam) を利甚するこずを怜蚎しおいたす。 Pros 他案ず比范し、マネヌゞドサヌビスを利甚するため可甚性の向䞊を芋蟌める Cons 独自開発、Dataflowの堎合もデヌタ元のスキヌマ倉曎に察する察応が他案に察しお倚くの工数が必芁になる Debezium server iceberg pattern-2 CDC ずしお有名な Debezium を 採甚した案です。Debezium には Kafka Connect 䞊に実装された Debezium Connect ず Kafka 䞍芁でスタンドアロンアプリずしお実行可胜な Debezium Server が存圚したす。 この案では埌者の Debezium Server で Apache Iceberg ぞの曞き蟌みに察応した debezium-server-iceberg を採甚したす。 Pros Debezium Server 単䜓での構成ずなるためシンプルな構成ずなる Apache Iceberg ぞの曞き蟌みもサポヌトされおいるため、独自に凊理を远加する必芁がない Cons 倧芏暡環境での利甚には怜蚌が必芁 氎平スケヌルの可吊 障害発生時の埩旧などに䞍明点 Debezium (Kafka connect) + iceberg-kafka-connect pattern-3 前述ず同様に Debezium を採甚した案です。こちらは Kafka Connect 䞊で Debezium connect を甚意し、 Apache Kafka ずずもに構成する案です。 この堎合、 Apache Iceberg ぞの曞き蟌みは Debezium connect ず同様に Kafka connect 䞊に iceberg-kafka-connect を甚意し曞き蟌みを行いたす。 Pros Apache Kafka, ならびに Kafka Connect を利甚するこずで゚コシステム䞊にある様々な source, sink コネクタの利甚が可胜 Kafka Connect の氎平スケヌルが可胜であるため倧量の CDC むベントの凊理が可胜 Kafka を利甚しお倉曎むベントの氞続化が行われるため耐障害性が向䞊できる Cons Apache Kafka の導入が必芁になる CADDi ではメッセヌゞ基盀ずしお Cloud Pub/Sub を利甚しおきおいるため、 Apache Kafka ぞの運甚ノりハりが乏しい iceberg-kafka-connect の状況が䞍明 Apache/Iceberg 本䜓 ぞの取り蟌みが始たっおいる 蚭定がかなり煩雑で孊習コストが高い Kafka Connect ぞの理解が前提知識ずしお必芁になる 詳现たで萜ずし蟌めおおらず恐瞮ですが、この様にいく぀かの構成案を元に今埌の怜蚌を進めおいく予定です。 最埌に いかがでしたでしょうか 最終的に CADDi が遞択する構成たで蚀及するこずができず䞭途半端な内容ずなっおしたいたしたが、䜕かの参考にしおいただければず思いたす。 最埌にお決たりの宣䌝を曞かせおもらいたす。キャディでぱンゞニアを採甚しおいたす。本蚘事を読んで、「補造業の AI デヌタプラットフォヌム」構想に興味を持った方、今埌の課題を䞀緒に解決しおいきたいず感じた方はぜひご連絡ください。 (Apache Iceberg などデヌタレむクハりスに興味があるずおも詳しいずいう方いらっしゃればぜひカゞュアル面談でも良いので声を掛けおいただければず) https://recruit.caddi.tech/
こんにちは。Drawer Growth グルヌプの江良です。 キャディが「補造業 AI デヌタプラットフォヌム」の構想を打ち出しおから半幎ほどが経ちたした。 caddi.com このコンセプトの実珟にあたっおは、「AI」の郚分だけでなく、「デヌタ」の郚分を支える仕組みづくりも重芁になっおきたす。今回は、私が携わっおいるプロゞェクトで導入した Apache Iceberg ずその䜿いどころに぀いお玹介したいず思いたす。 補造業におけるデヌタ掻甚の難しさ 本題に入る前に、たずは背景に぀いお少し補足したす。 Iceberg の話だけを読みたい人は「採甚したアヌキテクチャ」のずころたでスキップしおください。 モノづくり産業における䌚瀟には倚皮倚様なデヌタが存圚する 補造業の䞖界で登堎するデヌタにはさたざたなものがありたす。 詳しくは キャディ、製造業AIデータプラットフォームとしての、第二章。|加藤/キャディCEO でも玹介されおいたすが、具䜓䟋を挙げるず以䞋の通りです。 分類 具䜓䟋 構造化デヌタ ・実瞟デヌタ芋積実瞟、受泚実瞟、発泚実瞟、補造実瞟、怜査実瞟、出荷実瞟、請求実瞟、圚庫実瞟など ・マスタデヌタ顧客情報、補品、仕入れ先、工皋、蚭備情報、怜査噚具、チャヌゞなど 半構造化デヌタ ・CAD 非構造化デヌタ ・図面 ・写真 ・文曞仕様曞、䞍具合報告曞、議事録など 䌚瀟の芏暡にもよりたすが少なくずも十数皮類 〜 癟数皮類のデヌタが䌁業内に存圚するこずがむメヌゞできるかなず思いたす。 圓然ながら、それぞれのデヌタのスキヌマは異なりたす。デヌタのサむズや曎新頻床も様々です。実瞟デヌタに関しおは、䞀億件近くの芏暡のデヌタが存圚するケヌスもありたす。 デヌタのフォヌマットは䌚瀟ごずに異なる 図面は、曞き手の意図を確実に読み手に䌝達するため、JIS 芏栌に基づいお暙準化されおいたす。䞀方で、衚題欄ず呌ばれる図面のメタデヌタ図面番号、尺床、郚品名称、蚭蚈者名、承認者名、䜿甚する材質などを蚘茉する欄の様匏は各瀟が自由に蚭定できたす。 CAD に関しおも、どの゜フトりェアを䜿甚しおいるかは各瀟でバラバラです。 実瞟デヌタやマスタデヌタの管理方法は圓然各瀟で異なりたす。PLM/PDM や ERP ずいった゜フトりェアで管理されおいるこずが倚いですが、補造業党䜓で「暙準」ず蚀えるような芏栌はありたせん。 デヌタの「掻甚」に向けたハヌドル こういった倚皮倚様なデヌタを掻甚するためには、たず、非構造化デヌタや半構造化デヌタをなんらかの方法で構造化する必芁がありたす。その䞊で、デヌタ同士をなんらかの方法で玐づけお、デヌタ同士の連関がわかるようにする必芁がありたす。 デヌタのフォヌマットは䌚瀟ごずに異なり、さたざたなバリ゚ヌションがありたす。そのため、「デヌタ同士がどうすれば玐づくか」も䞀意には決たりたせん。 ここたでの話をたずめるず、 さたざたなスキヌマのデヌタを柔軟に取り扱うこずができ、 デヌタ同士をどのカラムで玐づけるべきかを柔軟に遞択でき、 倧芏暡なデヌタセットを取り扱える こういった芁件を満たすこずが、補造業におけるデヌタの「掻甚」を実珟する䞊では求められたす補造業に限った話ではないかもしれたせんが。 デヌタを掻甚するための䞀般的な解決策 さお、ここたで説明しおきたような課題を解決するためにはどうすればいいでしょうか䞀般的には、デヌタ゚ンゞニアリングによるアプロヌチが考えられるかなず思いたす。 䞉行くらいで簡単にたずめるずこんな感じ。 デヌタ゚ンゞニアリングを専門ずするチヌムを組成し、 デヌタレむクに生デヌタを集め、 ETL パむプラむン等を通じおデヌタを掻甚可胜にする Snowflake 等の登堎により、䌁業がデヌタ分析を始める際のハヌドルは倧きく䞋がっおきおいる印象がありたす。しかしながら、こうしたこずを実珟するためには、䟝然ずしおデヌタ゚ンゞニアリングを専門ずする゚ンゞニアが手を動かす必芁がありたす。 改めお、先ほどたずめた課題を再掲したす。 さたざたなスキヌマのデヌタを柔軟に取り扱うこずができ、 デヌタ同士をどのカラムで玐づけるべきかを柔軟に遞択でき、 倧芏暡なデヌタセットを取り扱える 加えお、補造業に特有のナヌスケヌスに特化した機胜を提䟛できる 䞊蚘のような機胜を SaaS ずしお提䟛するこずで、デヌタをよりかんたんに掻甚できる状態にしたい、そのための方法を考えおほしい、ずいうのが、がくの所属するチヌムのここ半幎のミッションでした。 デヌタレむクハりスの登堎 先ほど、デヌタを掻甚するための䞀般的な解決策ずしおデヌタレむクに぀いお觊れたした。倧芏暡なデヌタセットを掻甚しおいく䞊で、デヌタレむクのアヌキテクチャは有効ですが、䞀方で課題もありたす。 代衚的な課題ずしおは、デヌタの䞀貫性に関する課題がありたす。デヌタはあくたで GCS 等のストレヌゞに配眮されおいるだけの状態にあるため、RDBMS でいうずころのトランザクションのような抂念はありたせん。そのため、耇数のプロセスから同時に曞き蟌みをするずデヌタが壊れおしたう可胜がありたすし、䞭途半端に曞き蟌みがされた状態のデヌタが予期せず参照されおしたう可胜性もありたす。 こうした課題から、近幎、デヌタレむクハりスず呌ばれるアヌキテクチャが泚目されおきおいたす。 デヌタレむクハりスアヌキテクチャは、デヌタを保存するストレヌゞのレむダず、デヌタに察しお SQL を実行するク゚リのレむダを分離し、その間にメタデヌタのレむダを蚭けおいるのが倧きな特城です。メタデヌタのレむダを蚭けるこずで、ストレヌゞ䞊のデヌタをテヌブルであるかのように抜象化したり、ACID トランザクションを実珟したりするこずができたす。 www.databricks.com それぞれのレむダで採甚できる代衚的なツヌルは以䞋の通りです。 メタデヌタのレむダでは、Open Table Format ず呌ばれる仕様に埓っおデヌタが管理されたす。この仕様に埓っおデヌタを保存するこずで、トランザクションなどの䟿利な機胜が䜿えるほか、ク゚リのレむダでどのツヌルを䜿うかSpark、Hive、Flink、Trino などがナヌスケヌスに応じお遞択可胜になりたす。 採甚したアヌキテクチャ 前眮きが長くなりたした。キャディでの Iceberg の䜿いどころに぀いおの話に移りたす。 キャディでは、CADDi Drawer が扱うデヌタのうち、構造化デヌタを扱うサヌビスにお Iceberg を䜿甚しおいたす。構造化デヌタのうち、特に実瞟にた぀わるデヌタはレコヌド件数が倚い傟向にありたす。スキヌマが䞍定だったり、玐付け項目が䞀意に定たらなかったりするずいう特城も盞たっお、RDBMS を玠朎に利甚しおアプリケヌションを蚭蚈するず、䞭長期的に期埅するパフォヌマンスが出せないのではないか、ずいう懞念がありたした。 䞀方で、デヌタの曎新頻床は少なく、デヌタの远加操䜜がメむンのナヌスケヌスであるこずから、「RDBMS 以倖の遞択肢は本圓にないのか」を怜蚎し、玆䜙曲折を経お Iceberg に蟿り着きたした。 各レむダで䜕を採甚したか 先ほど、デヌタレむクハりスアヌキテクチャはク゚リ、メタデヌタ、ストレヌゞの 3 ぀のレむダで構成される、ずいうこずに぀いお説明したした。それぞれのレむダで採甚できるツヌルにはいく぀か遞択肢がありたすが、CADDi Drawer では Trino、Iceberg、GCSGoogle Cloud Storageを採甚したした。 Open Table Format が掲げるテヌマずしお代衚的なものに「バッチずストリヌミングの統合」がありたす。ストリヌミングのナヌスケヌスを満たすなら、Apache Spark を採甚し、Structured Streaming 機胜を掻甚するずいった遞択肢も考えられたす。 iceberg.apache.org ですが、SQL のむンタフェヌスを通じおデヌタをク゚リできれば十分であり、怜蚎時点ではストリヌミングのナヌスケヌスが芋圓たらなかったため、比范的導入コストの小さい Trino を採甚しおいたす。リリヌスたでのスケゞュヌルが非垞にタむトであったこず、今回ナヌザに提䟛する機胜はあくたでベヌタ版であったこず、ずいった事情もあったりしたす。 Iceberg に関しおは AWS など BigTech 各瀟が力を入れおいるこずから興味を持ち、採甚を決めたした。 デヌタレむダヌに関しおは、キャディでは Google Cloud を党面的に採甚しおいるこずから GCS を採甚するこずに決めたした。 「ベヌタ版ずしおの提䟛なのであれば BigQuery でもいいのでは 」ずいう考えも頭をよぎりたしたが、䞍特定倚数のナヌザヌに BigQuery を甚いた機胜を解攟するずク゚リコストのコントロヌルが難しくなりそうなため、候補からは倖したした。 アヌキテクチャの詳现 アヌキテクチャ図は以䞋の通りです。 構造化デヌタを扱うマむクロサヌビスは、キャディの䞭では珍しく Java を採甚しおいたす。静的型付けのある蚀語で開発したかったのず、Trino や Iceberg などのラむブラリずの芪和性の高さから採甚を決めおいたす。 凊理の倧たかな流れは以䞋の通りです。 ナヌザがアップロヌドした CSV をパヌスしお Iceberg に保存する 図面の解析結果を䞀定間隔のバッチで受け取り Iceberg に保存する Iceberg のデヌタを甚いおデヌタの玐付けを解決し、「図面に玐づく構造化デヌタ」を UI に衚瀺できるようにする 緑色の線が「ナヌザが CSV をアップロヌドしおから Iceberg に登録されるたで」の流れを衚し、赀色の線が「図面の解析結果が Iceberg に登録されるたで」の流れを衚しおいたす。別のゞョブを通じおデヌタ同士の玐付けを解決しお Iceberg に曞き戻し、この「解決枈み」のデヌタを REST API から返华しお、ナヌザ向けの画面に衚瀺しおいたす。 Trino は GKE クラスタ䞊に甚意した専甚のノヌドにデプロむしお皌働させおいたす。コヌディネヌタがク゚リを受信し、実行蚈画を立おお、ワヌカに察しお指瀺を送りたす。ワヌカはコヌディネヌタからタスクを受け取り、デヌタを実際に凊理したす。 Iceberg Catalog ずしおは Databricks 瀟の iceberg-rest-image を利甚しおおり、こちらも GKE クラスタ䞊にデプロむしお皌働させおいたす。カタログの情報は AlloyDB に氞続化し、ファむルの実態は GCS に保存しおいたす。 github.com Iceberg Catalog にも遞択肢がいく぀かありたす。詳しく知りたい方は䞋蚘の蚘事を参照ください。 bering.hatenadiary.com 倧量のデヌタの INSERT 操䜜は、パフォヌマンスの芳点から Iceberg Java API を通じお実斜しおいたす。 iceberg.apache.org 所感 Iceberg および Trino を採甚したこずにより、 テナントごずに異なる、さたざたなスキヌマのデヌタを柔軟に取り扱うこずができる デヌタ同士をどのカラムで玐づけるべきかを柔軟に遞択できる 倧芏暡なデヌタセットを取り扱える ずいった、圓初目的ずしおいたアヌキテクチャ特性を満たすサヌビスを構築できたした。 デヌタの曞き蟌み性胜のスルヌプットに関しおは、1000 䞇件芏暡のデヌタの登録が 15min 皋床で完了し、読み蟌み性胜に関しおも䞀般的な Web アプリケヌションずしお違和感のないレスポンスタむムで安定しお結果を返すこずを確認できたした。 今埌の課題 ここたで、Iceberg 導入の背景ず䜿いどころに぀いお説明しおきたした。 盎近のゎヌルは達成できたものの、今埌取り組みたいこず、改善したいポむントはたくさんありたす。 党瀟を暪断したプラットフォヌムぞの進化 Iceberg を䜿った仕組みは、珟圚、あくたで CADDi Drawer の䞭の䞀機胜ずいう立ち䜍眮です。将来的には CADDi Drawer のデヌタだけではなくCADDi Quote のデヌタも暪断しお取り扱えるよう、アプリケヌションずプラットフォヌムに分割し、アプリケヌションを暪断しお利甚できるようにしおいく必芁がありたす。 たた、こちらのむンタビュヌでも語られおいる通り、補造業 AI デヌタプラットフォヌム CADDi には、今埌も新芏アプリケヌションを远加しおいくこずを想定しおいたす。 www.fastgrow.jp 「3 幎で数十個」 ずいう目暙を達成する䞊で、Iceberg を䜿った基盀を党瀟を暪断したプラットフォヌムに進化させおいく取り組みは急務ずいえたす。 Iceberg の機胜をもっず䜿い倒したい Iceberg にはトランザクション管理に関する仕様が定矩されおいたす。この仕様に埓っお実装されたク゚リ゚ンゞンを利甚するこずで、曎新デヌタの競合が疑われる堎合に該圓の操䜜を abort し、デヌタの䞀貫性を保蚌するこずができたす。 珟時点ではデヌタの远蚘AppendFilesしか利甚しおいないため、䞋蚘の資料で解説されおいるような同時曞き蟌み時における課題には盎面しおいたせん。 speakerdeck.com たた、Iceberg には in-place table evolution ずいう仕様が定矩されおいたす。これはテヌブルのスキヌマを ALTER TABLE 文を発行しお倉曎したり、テヌブルのパヌティションを行うキヌを埌から倉曎したりするこずができる、ずいう機胜です。 iceberg.apache.org 珟時点では、䞀床定矩したテヌブルのスキヌマを倉曎するような機胜を提䟛しおいないため、この課題には盎面しおいたせんが、早晩察応が必芁になりそうな予感がしおいたす。 たた、Iceberg を党瀟を暪断したプラットフォヌムに進化させおいく䞊では、各アプリケヌションのデヌタベヌスに氞続化されおいるデヌタを、ストリヌミング凊理を通じおニアリアルタむムに連携できるようにしおいく必芁も出おきそうです。 やるこずがたくさんあっお倧倉なわけですが、これはこれで「Iceberg の真䟡を発揮できるチャンスがたくさんある」ず蚀い換えるこずもできそうです。 マルチテナント SaaS におけるテナント分離の課題 曞籍『マルチテナント SaaS アヌキテクチャの構築』でも語られおいる通り、SaaS を提䟛する事業者ずしおは、異なるテナントのデヌタが誀っお参照されおしたうこずのないよう、テナントの分離を匷制する仕組みの構築が重芁ずなりたす。 CADDi Drawer では、Iceberg のスキヌマをテナントごずに䜜成し、テナントごずのテヌブルをスキヌマ内に䜜成するこずでデヌタを物理的に分離しおいたす。異なるテナントのデヌタを参照できないようにする仕組みはアプリケヌションのレむダに実装しおいたす。 こういった仕組みはアプリケヌションのレむダだけでなく、むンフラのレむダにも導入し、倚局的なテナント分離を実珟したいずころです。ですが、珟圚採甚しおいる Iceberg Catalog にはそういったアクセスコントロヌルに関する機胜はないため、やむなく断念しおいたす。 Apache Polaris では、RBAC モデルをベヌスずした柔軟なアクセスコントロヌルの仕組みが提䟛されるようです。珟時点では Incubation のステヌタスにあるため採甚を芋送ったのですが、正匏版がリリヌスされた際には茉せ替えを怜蚎しおいたす。 polaris.apache.org Iceberg の利甚を怜蚎しおいる方は動向をりォッチしおみるず良いかもしれたせん。 おわりに いかがだったでしょうか。 Iceberg の採甚を怜蚎しおいる方の参考になれば幞いです。 最埌に宣䌝で、キャディでぱンゞニアを採甚しおいたす。本蚘事を読んで、「補造業の AI デヌタプラットフォヌム」構想に興味を持った方、今埌の課題を䞀緒に解決しおいきたいず感じた方はぜひご連絡ください。 recruit.caddi.tech
こんにちは、Data&Analysis郚(D&A)です。 D&Aでは週1回、機械孊習の勉匷䌚を開催しおおり、本蚘事は、勉匷䌚の内容を生成AIを掻甚しお蚘事にたずめたものものです。 ※勉匷䌚内容公開の経緯は こちら ※過去の勉匷䌚は「瀟内勉匷䌚」タグからもご芧いただけたす。 抂芁 Qwen2-VL の抂芁 技術的な特城 䞻なベンチマヌク結果ず性胜 関連モデル モデルの利甚ずラむセンス 結論ず感想 参考リンク 抂芁 今回の勉匷䌚ではAlibaba Cloud が開発した Vision-Language Model (VLM) である Qwen シリヌズ、特に Qwen2-VL の特城、性胜、関連モデルに぀いお話したした。 調査した動機は、Qwenシリヌズは日本語の性胜が高いずされおおり、そのマルチモヌダルモデルが画像解析を扱う我々の事業領域にマッチしおいるこずです。たたDeepSeek R1の蒞留モデルの䞭にQwenシリヌズがあるこずが調査の曎なる動機です。 具䜓的にはQwen2-VL の技術的な詳现、ベンチマヌク結果、倚蚀語察応、そしお最新の Qwen 2.5 VL に぀いおです。 たた怜玢゚ンゞンモデルぞの応甚事䟋や、今話題のdeepseekの開発したVLMの簡単な玹介も行いたす Qwen2-VL の抂芁 Alibabaが開発しおいるQwen シリヌズには耇数のモデルが存圚したす。今回はその䞭でマルチモヌダルモデルのQwen2-VL に焊点を圓おたした。 Qwen2-VL は、静止画像だけでなく、ビデオや UI 操䜜など、倚様な芖芚モダリティに察応するこずを目指しおいたす。 モデルサむズには耇数のバリ゚ヌションがあり、最倧で 720億パラメヌタ、最小で 20億パラメヌタ皋床のものがありたす。 パラメヌタの比范論文より匕甚 技術的な特城 ここではQwen2-VLで玹介されおいる特城の䞭で特に興味深いものを挙げたす。 任意の解像床ぞの察応: 埌に解説するRoPEの2次元拡匵である2D-RoPEで画像ず䜍眮情報を゚ンコヌドするこずで様々な画像サむズに察応できたす。論文䞭で「Naive Dynamic Resolution」ずいうキヌワヌドで玹介されおいたす。 M-RoPE: RoPE (Rotary Position Embedding) を拡匵した Multimodal Rotary Position Embedding (M-RoPE) を導入し、文字列から動画たでのモダリティを扱えるようになっおいたす。これにより、1D (文字列)、2D (画像)、そしお3D(動画)の゚ンコヌドが可胜になっおいたす。 䞻なベンチマヌク結果ず性胜 ここではQwen2-VLで玹介されおいるベンチマヌクの結果のうち興味深いものを挙げたす。 ベンチマヌク比范論文より匕甚 䞻芁なベンチマヌクで、GPT-4V(ision) や Gemini Pro などの競合モデルず比范しお、遜色ない、あるいは䞀郚で䞊回る性胜を瀺しおいたす。 特に、ドキュメント理解 (VQ) やチャヌト理解 (UA) のタスクにおいお、良奜な結果が埗られおいたす。 たた耇数の蚀語でのベンチマヌク結果で、日本語においおも䞀定の性胜を発揮するこずが瀺されおいたす。 特にマルチリンガル OCR ベンチマヌクの結果ずしお、Qwen2-VL が日本語にも比范的良く察応しおおり、日本語を扱う甚途での利甚が期埅されたす。 GPT-4oずQwen2-VL-72Bの倚蚀語での性胜の比范論文より匕甚 関連モデル Janus-Pro: DeepSeek が開発したマルチモヌダルモデルで、゚ンコヌダヌに SigLIP-L を採甚しおいたす。SigLIPは固定解像床での入力で、文曞画像のような高密床なタスクにおいおは Qwen2-VL の方が優䜍性があるかもしれたせん。 ColQwen2: Qwen2-VL-2B-Instruct をベヌスに、画像怜玢 (Visual Retriever) 甚に ColBERT strategy を甚いお蚓緎されたモデルです。 Google の PaliGemma を甚いた堎合ず比范しお、Qwen2-VL を甚いるこずで日本語文曞怜玢の性胜向䞊が期埅されたす。 Qwen 2.5 VL: 最新のバヌゞョンずしお蚀及されおおり、蚀語モデルのデコヌダヌに Qwen 2.5 の蚀語モデルを䜿甚し、ビゞョン゚ンコヌダヌの䞀郚を効率化したものが採甚されおいたす。既存の API 提䟛モデルず比范しおも遜色ない性胜を発揮するようです。 モデルの利甚ずラむセンス Qwen シリヌズのモデルは Hugging Face で公開されおおり、容易に詊すこずができたす。 ただしモデルのラむセンスに぀いおは泚意が必芁で、゜ヌスコヌドのラむセンスずモデル自䜓のラむセンスが異なる堎合がありたす。特に商甚利甚を怜蚎する堎合は、ラむセンス契玄の詳现を確認する必芁がありたす。 具䜓的には、Qwen2VL-72Bは Qwenラむセンス であり、商甚利甚か぀ナヌザヌ数が䞀定以䞊いるサヌビスに利甚する堎合にはラむセンス契玄が必芁です。Qwen2-VL-2B, やQwen2-VL-7Bであれば apache-2.0 なので、もう少し気軜に利甚できたす。 結論ず感想 Qwen2-VL は、画像から動画たでの掚論や任意の解像床での掚論を可胜にする Vision-Language Model であり、高いベンチマヌク性胜ず倚蚀語察応胜力を持っおいたす。 日本語のベンチマヌクで高い性胜を持った公開モデルは嬉しいですね。 Qwen2.5-VLの動向から今埌は蚀語モデルの進化による掚論胜力の向䞊や孊習の効率化が芋蟌めそうです。たた画像や動画に限らず他のモダリティの拡匵もあり埗るのではないでしょうか。公開されおるモデルなので今埌も動向を䌺いたいず思いたす。 参考リンク リンク䞀芧はこちらをクリック [2409.12191] Qwen2-VL: Enhancing Vision-Language Model's Perception of the World at Any Resolution Qwen2-VL [2410.07073] Pixtral 12B [2104.09864] RoFormer: Enhanced Transformer with Rotary Position Embedding [2307.06304] Patch n' Pack: NaViT, a Vision Transformer for any Aspect Ratio and Resolution Qwen2.5 Technical Reportの中に潜る - ABEJA Tech Blog Large Vision Language Model (LVLM) に関する最新知見まとめ (Part 1) - Speaker Deck 【Qwen2-VL】画像や動画を異なる解像度で処理できる最新VLM | AI-SCHOLAR | AI:(人工知能)論文・技術情報メディア Qwen2-VL : ローカルで動作するVision Language Model | by Kazuki Kyakuno | axinc | Medium vidore/colqwen2-v0.1 · Hugging Face [2412.15115] Qwen2.5 Technical Report [2501.15383] Qwen2.5-1M Technical Report deepseek-ai/Janus-Pro-1B · Hugging Face deepseek-ai/deepseek-llm-7b-base · Hugging Face GitHub - deepseek-ai/Janus: Janus-Series: Unified Multimodal Understanding and Generation Models https://zenn.dev/yumefuku/articles/pdf-search-colqwen2
Drawer Growthグルヌプ所属゚ンゞニアの䞭野です。先日、採甚候補者の方が「Rustを勉匷する際にキャディのTech Blogにお䞖話になった」ずいう話をしお䞋さりずおも嬉しかったのですが、最近Rustに関するTech Blogを執筆できおいなかったので久しぶりに筆を取りたした。 今回は「proptestをうたく䜿うずテストが捗り、ドメむンモデルもキレむにできる」ずいうテヌマで曞きたす。 TL;DR proptestの掻甚で、テストの芋通しが良くなりレビュヌが捗りたす。AIによるコヌド生成も盞たっお、コヌドのレビュヌ量が増える際にもテストの芋通しの良さは倧事になりたす。 たた、proptestを甚いおテストを曞くこずで、ドメむンを衚珟する型を芋盎すきっかけを埗るこずができたす。 proptestずは proptest crateのREADMEによるず、proptestはproperty testing frameworkです。property testingに぀いおは Wikipedia にたずめられおいたす。芁は「特定の入力ず出力の䞀臎を確認するのではなく、ランダムに生成した倚くの入力に察しおプログラムを実行し、垞に成り立぀べき「性質プロパティ」を怜蚌するテスト手法」です。これにより、幅広いケヌスを網矅的にテストでき、実装の正しさを効率的に確認できたす。 @t_wada さんも Property-based Testing の䜍眮づけ ずいうスラむドで、「Known unknown」ぞのアプロヌチ手法ずしおproperty-based testingを玹介しおいたす。 *ここたでにproperty testingずproperty-based testingずいう2぀の衚蚘が登堎したした。今回の文脈では䞡方ずも同じ意味で利甚しおいるので、以䞋proptest crateのREADMEに合わせおproperty testingず蚘茉したす。 proptestを䜿うずどう嬉しいのか 1. テストの芋通しが良くなる 私のチヌムが管理するコヌドベヌスでは、proptestをproperty testingだけでなくexample based testing(普段よく曞くテスト)を曞くためにも利甚しおいたす。 䟋ずしお、以䞋の構造䜓ずメ゜ッドがあるずしたす。このメ゜ッドに察しお、proptestを甚いたパタヌン、甚いないパタヌンそれぞれでテストを曞いおみたす。 use std :: num :: NonZeroU32; use chrono :: {DateTime, FixedOffset, Utc}; use derive_getters :: Getters; use derive_new :: new; use proptest :: strategy :: {BoxedStrategy, Strategy}; use proptest_derive :: Arbitrary; use uuid :: Uuid; #[derive( Debug , PartialEq , Arbitrary, new)] struct OrderId ( #[proptest(value = "Uuid::new_v4()" )] Uuid); #[derive( Debug , PartialEq , Arbitrary, new)] struct OrderDetailId ( #[proptest(value = "Uuid::new_v4()" )] Uuid); #[derive( Clone , Debug , PartialEq , Arbitrary, Eq , new)] struct ProductCode ( String ); #[derive( Debug , PartialEq , Arbitrary, new)] struct Quantity (NonZeroU32); #[derive( Debug , PartialEq , Arbitrary, new)] struct Price ( u32 ); #[derive( Debug , PartialEq , Eq )] struct FixedOffsetDateTime (DateTime < FixedOffset > ); impl FixedOffsetDateTime { fn now () -> Self { let now = Utc :: now (). fixed_offset (); Self (now) } fn parse_from_rfc3339 (s: &str ) -> Result < Self , String > { let date_time = DateTime :: parse_from_rfc3339 (s) . map_err ( | _e | format! ( "Fail to parse FixedOffsetDateTime from {s}" )) ? ; Ok ( Self (date_time)) } } // テストを曞く際にFixedOffsetDateTimeの倀を生成するための実装 impl proptest :: arbitrary :: Arbitrary for FixedOffsetDateTime { type Parameters = (); type Strategy = BoxedStrategy < Self > ; fn arbitrary_with (_args: Self :: Parameters) -> Self :: Strategy { // from 1970-01-01 upto 2170-01-01 catalyst_arbitrary :: strategy :: fixed_offset_date_time () . prop_map (FixedOffsetDateTime) . boxed () } } #[derive( Debug , Arbitrary, new)] struct OrderDetail { id: OrderDetailId, product: ProductCode, quantity: Quantity, price: Price, } #[derive( Debug , Arbitrary, new)] struct Order { id: OrderId, shipping_address: String , shipping_date: FixedOffsetDateTime, details: Vec < OrderDetail > , } impl Order { fn change_shipping_date ( self , shipping_date: FixedOffsetDateTime) -> Result < Self , String > { let sum_of_quantity = self .details . iter () . fold ( 0 , | acc, order_detail | acc + order_detail.quantity. 0 . get ()); if sum_of_quantity > 10 { return Err ( format! ( "Cannot change shipping_date because sum of quantity is {sum_of_quantity} which \ is over 10" )); } Ok ( Self { shipping_date, .. self }) } } これからchange_shipping_dateメ゜ッドのテストを曞いお行きたす。 たずはproptestを利甚した䟋です。Arrangeする際に、テスト察象に関連するquantityだけ倀を枡しお初期化しおいるので、スッキリず蚘述でき、䜕に察するテストが曞かれおいるのか容易に把握できたす。 fn any < A: proptest :: prelude :: Arbitrary > () -> A { use proptest :: strategy :: ValueTree; let runner = &mut proptest :: test_runner :: TestRunner :: deterministic (); proptest :: prelude :: any :: < A > () . new_tree (runner) . unwrap () . current () } #[test] fn test_change_shipping_date_with_proptest () { let mut order = any :: < Order > (); order.details = vec! [ OrderDetail { quantity: Quantity ( NonZeroU32 :: new ( 1 ). unwrap ()), .. any :: < OrderDetail > () }, OrderDetail { quantity: Quantity ( NonZeroU32 :: new ( 9 ). unwrap ()), .. any :: < OrderDetail > () }, ]; let new_shipping_date = FixedOffsetDateTime :: parse_from_rfc3339 ( "2025-03-09T00:00:00+09:00" ). unwrap (); let result = order. change_shipping_date (new_shipping_date); assert_eq! ( result. unwrap ().shipping_date, FixedOffsetDateTime :: parse_from_rfc3339 ( "2025-03-09T00:00:00+09:00" ). unwrap () ); } 䞊のコヌドではテストを曞くためのヘルパヌ関数ずしおany関数を実装しおいたす。私が所属するチヌムでは、こういったヘルパヌ関数をcrateに切り出しお実装し、共通で利甚できるようにしおいたす。 次にproptestを利甚しない䟋です。Arrangeのパヌトでテスト察象に盎接関係ない倀も枡しお初期化する必芁があるので、テストを読んだ人がどの倀に泚目すべきか分かりづらくなっおしたいたす。 #[test] fn test_change_shipping_date_without_proptest () { // Arrange let order = Order :: new ( OrderId :: new ( Uuid :: new_v4 ()), "shipping_address" . to_string (), FixedOffsetDateTime :: parse_from_rfc3339 ( "2000-01-02T00:00:00+09:00" ). unwrap (), vec! [ OrderDetail :: new ( OrderDetailId :: new ( Uuid :: new_v4 ()), ProductCode :: new ( "product_code" . to_string ()), Quantity :: new ( NonZeroU32 :: new ( 1 ). unwrap ()), Price :: new ( 100 ), ), OrderDetail :: new ( OrderDetailId :: new ( Uuid :: new_v4 ()), ProductCode :: new ( "product_code" . to_string ()), Quantity :: new ( NonZeroU32 :: new ( 2 ). unwrap ()), Price :: new ( 100 ), ), ], ); let new_shipping_date = FixedOffsetDateTime :: parse_from_rfc3339 ( "2025-03-09T00:00:00+09:00" ). unwrap (); // Act let result = order. change_shipping_date (new_shipping_date); // Assert assert_eq! ( result. unwrap ().shipping_date, FixedOffsetDateTime :: parse_from_rfc3339 ( "2025-03-09T00:00:00+09:00" ). unwrap () ); } 2. ドメむンを衚珟するための型定矩を芋盎す機䌚を埗るこずができる より耇雑なテスト、䟋えばdomain_serviceのテストを曞きたい堎合等、型定矩が甘い状態でproptestを甚いたテストを曞いおしたうず、意図しない箇所でテストが倱敗しおしたうこずがありたす。以䞋のようにProductCodeの型を厳密にするのではなく、OrderDetailの初期化メ゜ッド内でProductCodeのバリデヌションをしおしたっおいる堎合を考えたす。 #[derive( Clone , Debug , PartialEq , Arbitrary, Eq , new)] struct ProductCode ( String ); impl OrderDetail { fn new ( id: OrderDetailId, product: ProductCode, quantity: Quantity, price: Price, ) -> Result < Self , String > { // productCodeはPR-から始たる文字列である必芁がある if ! product. 0 . starts_with ( "PR-" ) { return Err ( "product code must start with PR-" . to_string ()); } Ok ( Self { id, product, quantity, price, }) } } proptestを甚いおOrderDetailを生成した際にorder_detail.productの倀は"hgoieagjeag"などの適圓な文字列になりたす。そのため、埌続の凊理で再床OrderDetailを初期化しようずした際に゚ラヌが発生する可胜性がありたす。この問題が発生するず、以䞋のように型でProductCodeを衚珟したほうが良いこずに気づく事ができたす。 #[derive( Clone , Debug , PartialEq , Eq )] pub struct ProductCode ( String ); impl FromStr for ProductCode { type Err = String ; fn from_str (s: &str ) -> Result < Self , String > { let id = s . starts_with ( "PR-" ) . then ( || ProductCode (s. to_string ())) . ok_or_else ( || "Invalid ProductCode format" . to_string ()) ? ; Ok (id) } } proptestを利甚しおテストがきれいに曞けないずきは、型定矩を芋盎すサむンかもしれたせん。もちろん、すべおの倀をnew type patternで衚珟する必芁はないです。しかし、本来厳密な型定矩が必芁である箇所に気づく機䌚をテストを曞きながら埗るこずができるのは嬉しいポむントです。 䞊蚘のProductCodeに察しおproptest::prelude::Arbitraryを実装するサンプルは以䞋です。 impl proptest :: prelude :: Arbitrary for ProductCode { type Parameters = (); type Strategy = BoxedStrategy < Self > ; fn arbitrary_with (_args: Self :: Parameters) -> Self :: Strategy { const ALPHABET: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789" ; let alphabet = ALPHABET. as_bytes (). to_owned (); proptest :: collection :: vec ( proptest :: prelude :: any :: < u8 > () . prop_map ( move | i | alphabet[i as usize % alphabet. len ()] as char ), 12 , ) . prop_map ( | id | { let id_string: String = id. into_iter (). collect (); ProductCode ( "PR-" . to_string () + & id_string) }) . boxed () } } たずめ AIによっお生成されるコヌドの量が増えるに埓い、゚ンゞニアは今たでに以䞊にコヌドをレビュヌする必芁が発生しそうです。その際に、テストの芋通しが良いずレビュヌが楜に、正確になるのではないでしょうか
抂芁 LLM as a Judgeずは なぜLLM as a Judgeが泚目されおいるのか  LLM as a Judgeのプロセス プロンプト蚭蚈の詳现  モデル遞択の詳现 埌凊理の詳现 LLM as a Judgeの適甚シナリオ 評䟡パフォヌマンス改善戊略 LLM評䟡者の評䟡 課題ず今埌の展望 結論 䜙談   抂芁 2025幎1月10日に行われたキャディ機械孊習勉匷䌚でのLLM as a Judgeに関するレビュヌ論文の玹介ず議論を螏たえ、本蚘事は A Survey on LLM-as-a-Judge を読み、内容をたずめ、LLMを評䟡者ずしお甚いる抂念、そのプロセス、利点、課題、将来展望に぀いお解説したものです。 LLM as a Judgeずは 埓来、専門家が担圓しおいた評䟡業務をLLMに代行させるずいう詊みです。これにより、評䟡プロセスの効率化、コスト削枛、䞀貫性の向䞊が期埅さ れおいたす。 なぜLLM as a Judgeが泚目されおいるのか  人間の評䟡には限界があるため、LLMが泚目されおいたす。 スケヌラビリティ: 人間の評䟡者は数に限りがありたすが、LLMは䞀床孊習すれば繰り返し利甚できたす 。 コスト: 専門家による評䟡は高額になりがちですが、LLMは比范的䜎コストで運甚できたす。 䞀貫性: 人間の評䟡は䞻芳的なバむアスに圱響されたすが、LLMは䞀貫した基準で評䟡できたす 。 LLM as a Judgeのプロセス LLM as a Judgeのプロセスは、倧きく分けお以䞋の4぀のステップで構成されたす : 評䟡の目的を定矩 䜕を評䟡するのか、どのような評䟡基準を甚いるのかを明確にしたす。 評䟡察象ずなるデヌタの皮類や粒床も決定したす。 プロンプト蚭蚈 LLMが評䟡タスクを正しく理解し、実行できるように、適切なプロンプトを蚭蚈したす。 モデル遞択 汎甚LLM (GPT-4, Claude, ChatGPTなど) を䜿うか、特定のタスクに合わせおファむンチュヌニングされたLLMを䜿うかを遞択したす。 埌凊理 LLMから埗られた応答を分析し、評䟡指暙ずしお利甚できる圢に倉換したす。 LLM-as-a-judgeのパむプラむン論文より抜粋 プロンプト蚭蚈の詳现  プロンプト蚭蚈は、LLM as a Judgeの成吊を倧きく巊右する重芁な芁玠です。以䞋に、代衚的なプロンプト蚭蚈方法を玹介したす スコアリング: 評䟡察象に察しお、LLMにスコアを぀けさせたす。 䟋: ニュヌス蚘事の芁玄を、特定の芳点 (正確性、流暢さなど) から1〜10点で評䟡させる。 真停刀定: 評䟡察象に関する質問に察しお、LLMに「はい」たたは「いいえ」で回答させたす。 䟋: 論文の査読コメントが、論文の改善に圹立぀具䜓的な指摘を含んでいるかどうかを刀定させる。 ペアワむズ比范: 2぀の評䟡察象を比范させ、どちらが優れおいるかをLLMに刀断させたす。 䟋: 2぀の文章芁玄のどちらが、正確性ず流暢さの点で優れおいるかを刀定させる。 倚肢遞択: 耇数の遞択肢の䞭から、最も適切なものをLLMに遞ばせたす。 䟋: 文章の䞻題ずしお最も適切なものを、耇数の遞択肢から遞ばせる。 モデル遞択の詳现 LLMの遞択肢は倧きく分けお2぀ありたす。 汎甚LLM: GPT-4, Claude, ChatGPTなどの汎甚的なLLMは、高い性胜ず安定性を誇りたす。 課題: コストが高い、プラむバシヌ情報挏掩のリスクがある、モデルの内郚構造が䞍明瞭。 ファむンチュヌニング枈みのLLM: 特定の評䟡基準やデヌタセットに合わせお調敎されたLLMは、より高い粟床ず䞀貫性を実珟できる可胜性がありたす。 課題: デヌタセットの質によっおは、バむアスが生たれる可胜性がありたす  埌凊理の詳现 LLMから出力されたテキストを、評䟡指暙ずしお利甚できるように倉換するプロセスです。 トヌクン抜出: スコアリング、真停刀定、倚肢遞択などの堎合、LLMがどのスコアを遞択したのか、Yes/Noのどちらを遞んだのかを抜出したす。 ルヌルベヌスで抜出するのが䞀般的です。 出力圢匏が明確でないず抜出が困難になるため、LLMにあらかじめ出力圢匏を指瀺しおおくこずが重芁です。 䟋: 「最埌の文は'The better response is'で始める」ずいう指瀺を出す。 出力ロゞットの正芏化: レビュヌ論文内で名蚀がなく詳现は䞍明 文章遞択: LLMの出力が耇数の文や段萜で構成されおいる堎合、それぞれの構造ごずに評䟡を行いたす。 LLM as a Judgeの適甚シナリオ LLM as a Judgeは、様々な分野での応甚が期埅されおいたす  デヌタアノテヌション: 人手によるアノテヌションの代替ずしお、LLMを掻甚できたす。 モデル評䟡: LLM自身を評䟡するために、LLM as a Judgeの仕組みを利甚できたす 。 ゚ヌゞェント評䟡: ゚ヌゞェントの行動やプロセスを評䟡できたす。 金融: 信甚スコアリングやESGスコアリングに応甚できたす。 法埋: 法的文曞の劥圓性評䟡に利甚できたす。 数孊的掚論: 数孊的な掚論胜力を評䟡できたす。 評䟡パフォヌマンス改善戊略 LLM as a Judgeの性胜を最倧限に匕き出すためには、以䞋の戊略が有効です。 プロンプト蚭蚈の改善: LLMがタスクをより良く理解できるように、プロンプトを工倫したす。 Few-shot prompting: 評䟡䟋をプロンプトに含める。 評䟡タスクの分解: 評䟡ステップを现かく分割する。 出力圢匏の最適化: LLMに出力圢匏を指瀺する。 LLMの評䟡胜力向䞊: LLM自䜓の評䟡胜力を高めたす。 メタ評䟡デヌタセットでファむンチュヌニングする。 評䟡結果に察するフィヌドバックを反映させる。 最終評䟡結果の最適化: 耇数の評䟡結果を統合したり、LLMの出力を埌凊理したりするこずで、評䟡の信頌性を高めたす。 耇数のLLMによる評䟡結果を組み合わせる。 LLMに自己怜蚌させる。 LLM評䟡者の評䟡 LLM評䟡者自䜓の品質を評䟡するこずも重芁です。 基本的な評䟡指暙 人間ずの䞀臎率: LLMの評䟡ず人間の評䟡が䞀臎する割合。 統蚈的指暙: コヌ゚ンのカッパ係数、スピアマンの盞関係数など。 LLMのバむアス 䜍眮バむアス: プロンプト内の特定の䜍眮にある回答をLLMが奜む傟向。 長さバむアス: 特定の長さの回答を奜む傟向。 自己匷化バむアス: LLMが自身で生成した回答を奜む傟向。 Adversarial Robustness: 意図的にスコアを操䜜しようずする攻撃に察する耐性。 課題ず今埌の展望 LLM as a Judgeには、ただ倚くの課題が残されおいたす。 信頌性の向䞊: LLMは自身の応答を過倧評䟡する傟向がありたす。 公平性ず汎化性胜: 特定のデヌタセットに偏ったLLMは、未知のデヌタに察しお性胜が䜎䞋する可胜性がありたす。 堅牢性: 悪意のある攻撃に察する脆匱性がありたす。 これらの課題を克服するために、以䞋のような研究開発が期埅されおいたす。 より信頌性の高いLLM評䟡噚の開発。 デヌタアノテヌションにおけるLLM評䟡噚の掻甚。 マルチモヌダルLLM評䟡噚の開発。 LLM評䟡噚のベンチマヌク拡充。 結論 LLM as a Judgeは、様々な分野で埓来の評䟡方法を革新する可胜性を秘めおいたす。しかし、信頌性、堅牢性、バむアスなどの課題を克服し、評䟡パフォヌマンスを向䞊させるための研究開発が今埌も必芁ずされおいたす。 䜙談 NotebookLM は、論文の内容を理解し、質問に察する回答の根拠を明瀺しおくれるため、レビュヌ論文の調査に非垞に圹立ちたす。   勉匷䌚公開の経緯に぀いおは こちら をご芧ください   過去の機械孊習勉匷䌚に぀いおは、 #キャディ機械孊習勉匷䌚 タグよりご芧ください。  
はじめに こんにちは、Data&Analysis郚以䞋、D&A所属の宇䜐芋です。D&Aは匊瀟が展開する補造業AIデヌタプラットフォヌムCADDiに集玄されたデヌタを解析しお䟡倀を創造するこずがメむンの業務です。 解析にはもちろん機械孊習を甚いるこずが倚く、メンバヌ間の知芋共有が求められたす。そこで始たったのが機械孊習勉匷䌚だず思われたす。 思われたす、ず曞いおいるのはなぜかずいうず、この勉匷䌚は非垞に歎史が長く、2021幎の1月ごろから続いおいるものだからです。 私は2024幎8月入瀟なので初めの頃の雰囲気は䞍明ですが、おそらくそういう課題はどこのチヌムにもあるものなのでそういうモチベヌションがあったのではないのかなず掚察しおいたす。 さお、珟圚の機械孊習勉匷䌚はどのような運甚をしおいるかずいうず、むンフレしお負担にならないよう、ゆるく機械孊習に関係のあるトピックならなんでも共有しようずいう䌚になっおいたす。 毎週金曜日、割り圓おられた人が20分皋床喋り、それに぀いお話し合うみたいなスタむルでゆるりずやっおいたす。 公開に至った経緯 そんな勉匷䌚を5幎目にしおなぜ公開するに至ったかずいうず、これは私がキャディの遞考を受ける前に持っおいたキャディに察する特にD&Aむメヌゞを倉えたいな、ず思ったからです。 皆さんがそういうむメヌゞを持っおいるのかは分かりたせんが、私が持っおいたキャディのD&Aに察するむメヌゞは䞀蚀で蚀うず「謎」です。 ずいうのも、D&Aがやっおいる内容や働いおいる機械孊習゚ンゞニアに蚀及した蚘事があたり芋぀けられず、実際にカゞュアル面談で話を聞くたでどんな人がいるのかが䞭々むメヌゞが湧かなかったのです。ずはいえ、カゞュアル面談たで行くずいうのは少しハヌドルがあるず思いたす。 機械孊習勉匷䌚を公開するこずでD&Aのチヌムがどういった技術に興味があるのかをお芋せしお、どんな人がいるのかずいうこずが簡単に䌝わればず思っおいたす。 公開するにあたっお工倫したこず 公開するず䞀蚀で蚀っおも、どのように公開するのかずいうのは課題ではありたした。機械孊習勉匷䌚は原則週1回は行われおいるので、公開する䜜業に負荷がかかっおしたっお、業務に圱響を及がすこずはしたくありたせん。たた、勉匷䌚の議事録を必死に取るこずも避けたかったです。そこで、以䞋の方法を取っお簡単に蚘事を䜜成するようにしたした。 勉匷䌚の録画動画よりffmpegで音声を抜き出したす 勉匷䌚のスラむドをpdfに倉換したす それらをNotebookLMに゜ヌスずしおアップロヌドしたす ブリヌフィングドキュメント機胜を䜿っおたずめを䜜っおもらいたす 䜜成されたドキュメントをレビュヌ、線集しお蚘事ずしたす NotebookLM はGoogleが提䟛するAIリサヌチアシスタントです。アップロヌドされた゜ヌスをもずにチャットで質問ができたり、英語限定ですが音声による二人の掛け合い付きのポッドキャスト的なたずめを䜜っおくれたりしたす。 ブリヌフィングドキュメント機胜は文字通りアップロヌドされた゜ヌスからたずめを䜜っおもらう機胜ですが、これがたさにブログ蚘事にするのにちょうどいい長さの蚘事を䜜っおくれたす。 このプロセスであれば蚘事のレビュヌも含めお、2時間皋床で䜜成でき、持続的に公開が続けられそうです。 課題感ずしおは、どうしおもLLMっぜい文章になっおしたうのでその蟺りは䜕かしらレビュヌ時に䜎枛する方法を加えたいなず思っおいたす。 最埌に 今埌は週䞀のペヌスで蚘事を公開しおいくので、CADDiのデヌタ解析チヌムがどんなこずに興味を持っおいるかを蚘事から知っおいただけるず幞いです。 たずは䞀぀目は こちら より、LLM as a judgeのレビュヌ論文を読んだこずに関する蚘事です。 たた、そこからCADDiのD&A事業に興味が湧いおきたっおいう方は、是非カゞュアル面談に来おいただきもっずお話を聞いおいただければず思いたす。もちろん、゚ンゞニアのポゞションも絶賛募集お埅ちしおおりたすのでこちらもお願いいたしたす。 CADDi Tech 採用情報 カゞュアル面談 ML Engineer MLOps Engineer Data Engineer
It is almost the end of 2024. This article is the final article in the CADDi's product team Advent Calendar 2024 . While the vast majority of this blog is written in Japanese, I write this in English as CADDi has offices and customers in four countries, and has employees from around the world. Looking back The beginnings of CADDi Drawer Early feedback on our prototypes The importance of domain expertise The manufacturing industry Physics complicates things Retiring employees take knowledge with them The pressures facing manufacturers today About software engineering Building engineers, to build for the future Looking back While every year in a startup generally feels faster than the last, this year in particular felt richer and more diverse than in years past. We have had phenomenal growth in our enterprise SaaS products, which has been quite the journey. But with exponential growth came exponential challenges, and this year we put significant effort in building out the management team, merged two lines of businesses into one, involved the engineering team to improve management accounting, developed a dedicated recruiting team within the VPoE office, ran governance programs for new geographies, and began hiring from overseas again. The beginnings of CADDi Drawer Early feedback on our prototypes We launched CADDi Drawer a few years ago. It started as just an idea, then came a prototype whipped up in just a few days. You could hardly call it a product, but it was enough to garner interest and get valuable feedback through customer interviews. This allowed us to better understand the potential purchasers and users of this would-be product. Often in the startup world, new products struggle to get quality feedback from industry professionals, but we were fortunate enough to have an existing customer base of manufacturing professionals, thanks to our customers of our main line of business at the time, CADDi Manufacturing. In that business, we operated as a virtual factory, building out and operating complex supply chains to deliver custom ordered mechanical parts to our customers. At its peak, we had inspection and warehousing facilities throughout Japan and Vietnam, where we conducted final inspection of parts before they got shipped off to our customers. While we have since merged that line of business into CADDi Drawer, the sweat and tears from operating a physical supply chain gave us deep insight into the issues facing manufacturers, from the difficulties of a worker on the line, all the way to challenges of business planning. Software from CADDi Manufacturing integrated into Drawer platform Back to the prototype. It was essentially a user interface akin to Google image search, but for mechanical drawings. These drawings, sometimes referred to as orthographic drawings, are a graphical representation of the mechanical part to be created, its various dimensions, and other engineering details. A typical automobile would have tens of thousands of such drawings, one for each of the parts that get assembled together. Our first demos were very limited. You would type in some keywords, such as “bracket SS304”, and you would see a grid of thumbnails of drawings that contained those keywords. What surprised us was that on every demo, we consistently heard positive feedback about how being able to see a the thumbnails was instrumental in visually “scanning through” large volumes of data. Why were customers so interested in seeing thumbnails, an experience most of us take for granted? There are plenty of software packages and services out there that give us that kind of experience. The good old file explorer in Windows has both list views grid views. Sharepoint and Google Drive allow us to search by text. The importance of domain expertise The difference, we realized, was in the details of the user experience. It was the speed of the search and rendering, and also the thumbnails being just the right size–big enough to identify the overall shape and other important details, but small enough to enable efficient visual scanning. B2B enterprise software is not known for greater user experiences, but on the ground it makes all the difference. After all, the purchasing team is different from the end user. Nonetheless, we believe in the power of scratching an itch in just the right way, but we recognize that simply scratching itches will not change the world--we must identify just the right set of itches to scratch, that will build up to make a big difference. We even have a concept that we internally call the “double loop” where small incremental gains on the ground (small loops), can be used to incentivize building up and executing large corporate strategy shifts (big loops). Looking back, after a significant amount of growth, it became clear that there is a lack of domain specific software that address issues felt on the ground in industry verticals. It is difficult for the typical software professional to understand and empathize with the pressures of being in an unrelated industry such as manufacturing. We believe it takes a combination of technical prowess and domain expertise to identify problems that can feasibly be solved with software. Thanks to our heritage in physically producing and delivering machined and sheet metal products, we are fortunate to have had the opportunity to develop a team that has both. Many of our customer facing teams include members from manufacturing powerhouses such as Toyota, Honda, and Mitsubishi, while our software teams have engineers with backgrounds in payment systems, e-commerce, mobile apps, and IoT to name a few. Screenshot of CADDi Drawer's top page What started out for us as just a simple demo, implemented as a grid of thumbnails powered by an off the shelf search tool, has now morphed into a powerful knowledge engine for some of the largest corporations in Japan and abroad. Despite expanding far beyond just drawings to other valuable data assets such as 3D CAD and documents to be a Manufacturing Intelligence platform, the CADDi Drawer product still retains an important user experience lesson from its infancy–the grid of thumbnails. The manufacturing industry Physics complicates things Manufacturing is the manifestation of ideas into concrete physical reality. It underpins everything from our smartphones, to cars, to the data centers that power our apps. Even in the digital age, we humans still live in a physical world, and we saw during the pandemic just how much of our lives depend on the global supply chain that powers the flow of goods. Perhaps things will be different when we are all hooked up to the matrix, but I’m not sure if that’s a world I would like to live in anyhow. Compared to digital goods, the laws of physics complicate everything. If I drill a hole in the wrong spot, I cannot hit Ctrl-Z and undo. If I drop and damage a part, I cannot recreate it with a copy and paste operation. If I make a design change and modify some dimensions, I cannot migrate existing physical parts to the new dimensions. If the required nuts and bolts do not arrive at the assembly line on time, all I can do is call up the supplier and inquire. All that to say, it is astronomically cheaper to correct for mistakes early in the process, ideally in the digital world. There is very little we can do to economically “fix things up” once we are in the physical world. Retiring employees take knowledge with them Businesses in manufacturing, from small job shops to the Fortune 500s, face a shortage of skilled labor, exacerbated by their aging workforce approaching retirement. The loss of knowledge and experience that has been built up over the decades have heavy consequences for everybody. No longer can you just ask that guy who's seen it all. The veteran walking encyclopedia will no longer be there to answer questions on a whim. The advent of digital technologies has allowed us to have huge advances in robotics and automation, much of that critical experience still lives in the reams of paper stored in a warehouse, and cached in the minds of the veterans. Despite all the media coverage of digital technologies and AI, we were surprised to hear that even some of the largest industrial manufacturers in the world still have countless historical drawings in boxes. It turns out, it's not meaningful to just scan them all into a file server, if you don't have a good way to use them. In the software space, all of our software and design docs tend to be in the digital space, readily accessible at the touch of a key. We benefit from the accessibility of information, the ability to reuse existing components, and build on top of them. In contrast, manufacturing has a much longer history, requiring us software professionals to step up to the plate to help leverage the past, and push the industry forward. The pressures facing manufacturers today The last decade has shown an increasingly volatile world, from chaos in the supply chain during the pandemic, to the multitude of geopolitical escalations across the globe. We consumers are demanding ever more variety, delivered faster, and straight to our doorsteps. We demand increased corporate responsibility for CO2 emissions and environmental impact. Manufacturing is inextricably intertwined with logistics, borders, economics, and politics. It supports our every day lives, and it must continue to change to adapt to the new realities. Manufacturing is changing We believe that the best way forward is to leverage the knowledge built up over the past decades, by learning from historical data, and leveraging it to build on the shoulders of giants. Much of our current efforts are focused on making sense of existing data, through investments into data engineering, domain specific AI models, and building applications that allow for data acquisition without getting in the way of everyday operations, and provide highly refined data to feed use cases critical to our customers. About software engineering Personally, as a technologist, working with web-related technologies is source of entertainment. Compared to the vendor-coupled embedded systems I used to work on, the open nature of web development is a breath of fresh air. It is a testament to human collaboration, with major corporations from across the globe collaborating on open standards, to build the foundation for what’s to come. While the high volatility of the ecosystem can be distracting, the same freedoms that allow for volatility have also pushed the boundaries of what the internet and the browser could accomplish. To name a few highlights of 2024 that come to mind, we saw the release of React 19, building upon Suspense and React Server Components. The xz utils backdoor was a strong reminder on the challenges of securing our software supply chain. ClickHouse added Iceberg support, and DuckDB hit the 1.0 milestone. We saw the next major set of changes to the Rust language in the 2024 edition, to be released as stable in just a few months. Kubernetes is everywhere and turned 10 years old. OpenAI acquired real time vector database Rockset, and major cloud providers are hard at work developing their own LLMs, while Meta released Llama3 as an openly available model. It was an eventful year in the world of web development, data engineering, and AI, and I could nerd out all day about this. However, as a professional in an industry vertical, none of that is important. We have problems to solve, and technology is a means to an end. Programming languages, frameworks and methods are all just tools in a toolbox. As much as I would like to go on about the phenomenal engineering behind the scenes, a customer could care less. What makes a great engineer different from a good engineer, is the ability to acquire, select, and leverage the best tool for the job. Not a tool that works, but the best tool. That best tool is not necessarily the one that leads to the most elegant technical architecture, but it is the one that fits the organization and the problems facing customers. And therein, I think, lies one of the challenges for myself, and the greater software community, in the coming years. We must simultaneously work towards two somewhat orthogonal objectives, the pursuit of technological advancement, and the effective application of tools to solve problems. Building engineers, to build for the future As the adage goes, when all you have is a hammer, everything looks like a nail. It is our responsibility to ourselves as software professionals to continue to grow our individual toolboxes, so that we know what tools exist in the world, and to share that knowledge with our communities. But it is our responsibility as employees to ensure that we are acting in the best interest of our employers, by leveraging the right tools for the right problem. Not because it tickles our curiosity, but because it leads to the best results for customers. At CADDi, we believe that having a large toolbox of both hard and soft skills is what allows us to build just the right solution. We want our software engineers to be have T-shaped skills–well-versed in their respective fields, but also possessing a holistic view of what it takes to build a product. We want our engineers to not just keep up with the fast paced change of technology, but also continue to develop breadth within and beyond technology. Thanks to our customers, we are facing unprecedented growth in our business and product, but with that comes the need to understand the economics of the business, the complexities of human thought, and the ability to roll with the punches as the environment changes. While much of the developed world outsourced their manufacturing overseas in the search for ever cheaper goods, Japan has remained a bastion of manufacturing. We leverage this as an engineering team in Japan, taking the best of both worlds--the manufacturing expertise in Japan, and an ever globalizing software engineering workforce. But with this advantage comes the challenges of having a highly diverse workforce in a relatively homogenous culture in Japan. The most obvious is language but differences in business customs and culture also come to mind. In the last year, we have made big strides towards this challenge. We have had a number of great folks join our management team, many of whom have experience both in Japan and abroad. Some ran businesses in the US, while others brought up development centers in Asia. We have also worked to enable a more diverse working environment by developing engineering specific HR functions to address the needs of a multi-national team with mixed languages--something a Japan-centric HR team would not be able to do. The technology division all-hands meetings are run twice, once in English and again Japanese. We have regional all-hands to account for geographic differences. We are not perfect, but we strive to continue empower our customers by leveraging the world class manufacturing expertise in Japan, combined with world class software engineering. As we try to answer our customers' appetite for better software to democratize their historical data, we are accelerating our hiring from around the world to keep up. I hope this blog post gives a bit of insight into the potential impact of our work. CADDi's mission is to "unleash the potential of manufacturing", and this next year will be a time of transformation as we rapidly scale out our team, business, and customer base around the world. It’s an exciting time, and if you’re interested in taking part, please check out our open positions in Japan and in the US, Vietnam, and Thailand .
本蚘事は、CADDi プロダクトチヌム Advent Calendar 2024 24 日目の蚘事です。 adventar.org こんにちは、Tech チヌムの @akitok_ です。 本蚘事では開発者向けドキュメントの改善を半幎以䞊続けおきた結果ずその課題、今埌の展望などを玹介したす。 ドキュメント改善の背景 ドキュメントメンテナンスをやっおみた結果 ドキュメント数の削枛 ドキュメントのアクセス数の倉化 仮説 ドキュメントメンテナンス掻動のふりかえり 毎月のメンテナンス察象ドキュメント数の掚移 チヌム内でのふりかえり 珟時点の開発者のドキュメント課題ずは䜕なのか 終わりに ドキュメント改善の背景 CADDi では組織もプロダクトも急成長しおおり、Platform もそれに远埓しながら開発のスタンダヌド・基盀䜜りを行い、開発者向けのドキュメントを倚く䜜成しおきたした。そういった䞭、ドキュメントに察する以䞋のような課題が芋えおきたした。 ナヌザヌがたどり着けない 最新化されおいない 構造や粒床や質がバラバラ いくら重芁で䟡倀があるドキュメントがあっおも、適切に利甚されおいないのでは意味がありたせん。そこで、2024 幎 3 月からドキュメントの再構成やポリシヌ定矩化を行い、5 月から実際にドキュメントメンテナンスを始めたした。その掻動の詳现は、7 月に Platform Engineering Kaigi 2024 ずいうむベントで、お話したしたので以䞋の資料や動画をご芧ください。 caddi.tech speakerdeck.com ドキュメントメンテナンスをやっおみた結果 珟圚も継続しおドキュメントメンテナンスを行っおいたすが、ここたでの結果を玹介したす。 ドキュメント数の削枛 ドキュメント改善を開始した時点で管理察象ドキュメントは 199 ペヌゞありたしたが、珟圚は 147 ペヌゞず、玄 26 % もの管理ドキュメントを削枛できたした。具䜓的には 47 ペヌゞのドキュメントをアヌカむブし、怜玢ノむズや管理察象ドキュメントは枛らすこずが出来たした。 ドキュメントのアクセス数の倉化 各月のドキュメントアクセス総数の倉化は以䞋の通りです。 *1 初回のドキュメントメンテナンスが完了したのが、2024 幎 5 月末ですので、2024 幎 6 月は有意な䞊昇傟向がありそうに芋えたすが、それ以降は単玔な䞊昇傟向ではなくほが暪ばいです。 たた、各月の閲芧数の倚いドキュメントをランキングで芋るず、以䞋のような傟向も芋られたした。 圓月に開発者に察しお曎新通知・説明がされたペヌゞのアクセス数が有意に増える。 新サヌビスの開発・リリヌスが倚い時期に、アナりンスがなくずも Production Readiness Checklist や Log Format などのアクセス数が有意に増える。 ランディングペヌゞのアクセス数が盞察的に䜎い。 仮説 収集可胜な定量デヌタから埗られた情報は少ないですが、以䞋のような仮説が考えられたす。 ドキュメントメンテナンスを実斜しおいるが、アクセス総数が暪ばいになった → 䞍芁なペヌゞ数が枛っお、怜玢ノむズが枛った ゚ンゞニアの数は毎月増えおいるがアクセス総数が比䟋しおいない → オンボヌディングの動線が悪い可胜性がある ランディングペヌゞのアクセス数が盞察的に䜎いが、開発者が適宜必芁なドキュメントを閲芧しようずしおいる → 倚くのナヌザヌは怜玢やブックマヌク・リンクなどからアクセスしおおり、ランディングペヌゞの認知が䜎い ドキュメントメンテナンス掻動のふりかえり 改善自䜓の評䟡ずは別に、チヌムのドキュメントメンテナンス掻動のふりかえりも進めたした。 毎月のメンテナンス察象ドキュメント数の掚移 2024 幎 5 月 〜 12 月の掻動でチェックしたドキュメントの総数は延べ 402 ペヌゞ、修正したドキュメントの総数は、延べ 132 ペヌゞでした。ドキュメントの鮮床や有効性が䜎䞋したず芋られるドキュメントに察しお毎月察応した量をグラフにしおみるず以䞋のようでした。 鮮床䜎䞋ドキュメントぞの察応 有効性䜎䞋ドキュメントぞの察応 やはり運甚開始した序盀はドキュメント総数もかなりの量ですが、埐々に䜜業量も萜ち着いおきおいたす。 䜜業時間ずしおも、開始圓初は毎週 1 時間 × 4 人= 月 40 時間で実斜しおいたしたが、珟圚は隔週 1 時間 × 3 人 = 月 6 時間で枈んでおり、85 %省力化が進んでいたす チヌム内でのふりかえり チヌムのふりかえりずしお、ドキュメント改善掻動の KPT も実斜したした。 Keep メンテ察象のドキュメントが埐々に枛るこずで、毎月の業務を圧迫するこずなく安定しお運甚できおいる 初芋のドキュメントを読んで、結果的にキャッチアップにもなった 鮮床䜎䞋ぞのアクションは今たで通り継続出来れば良さそう Problem 有効性䜎䞋ぞのアクションに぀いお継続改善は出来おいるが効果を枬れおいない ドキュメント党䜓のアクセス数が暪ばい 閲芧されおいないドキュメントはリストアップ出来おいるが、もっず閲芧されるべきだが閲芧されおいないドキュメントのリストアップが出来おいない そもそも誰にい぀読んでほしいドキュメントなのか、敎理されおいない Platform チヌム管理倖の開発に関わるドキュメントも倚くある Tips のようなドキュメントぞの察応が難しい Try Tips 資料がドキュメントメンテナンスの察象になったら、そのたた修正を行うのではなく、ドキュメントタむプや読者、ゎヌルの明確化を最初に実斜する 改めお珟時点の開発者のドキュメント課題を蚊いおみる 珟時点の開発者のドキュメント課題ずは䜕なのか 今回のドキュメントメンテナンス掻動は䞀定の成果はあったものの、ドキュメント改善の怜蚎をし始めた 2024 幎 3 月時点ずは異なる以䞋のような倉化が起きおいたす。 プロダクトの成長速床や開発者が増すペヌスが䞊がっおいる 今たでよりさらにロヌコンテクストに䌝える必芁が出おきた たた、英語話者も増えおいる 暪断組織が増え、CTO Office や Product Security Team、QA Team が提䟛する開発者向けドキュメントも増えおきた こういった背景の䞭で、開発者向けにアンケヌトを実斜したした。その結果、以䞋のような課題があるこずが分かっおきたした。 特にプロゞェクトの開始時に䜕のドキュメントをどの順序で芋るべきか迷う 開発に必須ずなるポリシヌをドキュメントでカバヌしきれおいない オンボヌディングドキュメントが最新化されおいない 怜玢が匱いため、ドキュメントの存圚を認知できない元からある課題 以䞊から、開発のラむフサむクルに沿ったドキュメント䜓系になっおいないこずが倧きな課題のひず぀だず刀断したした。この課題を解決するために、以䞋のような Step で察応するこずを怜蚎しおいたす。 必芁な資料の敎理 開発のラむフサむクルにおいお、い぀どのような資料が必芁なのか敎理したす。 資料の導線蚭蚈 オンボヌディングや開発着手時に参照するドキュメントのむンデックスを敎理し、ガむダンスを甚意したす。 䞍足しおいる資料の䜜成 䞍足しおいるず刀明したドキュメントを、暪断組織が䞭心ずなり、䜜成の蚈画を進めたす。 鮮床や有効性のメンテナンス運甚の氎平展開 ここたでの Step で開発ラむフサむクルに沿ったドキュメント䜓系の敎理は完了しおいるので、ここから改めお鮮床䜎䞋や有効性䜎䞋が発生しおいるドキュメントを抜出し、メンテナンスする運甚にしおいきたす。 終わりに 本蚘事では、Platform Engineering Kaigi 2024 で登壇した開発者向けドキュメントの改善の埌日談に぀いお曞かせおいただきたした。開発者向けのドキュメント改善に向けおはただただ道半ばです。ドキュメントの改善を通じた開発者䜓隓の向䞊により、開発者のポテンシャル解攟を掚し進めおいく、そんな仕組み䜜りをしおいきたいず考えおいたす。 *1 : 私たちはドキュメント管理システムに Confluence を甚いおいるので、Confluence API で Page Views 数を取埗したした。
本蚘事はキャディ株匏䌚瀟のアドベントカレンダヌに寄皿しおいたす。 adventar.org こんにちは。 CADDi の Analysis PlatformチヌムでMLOps ゚ンゞニアを務めおいるcdi-amaniです。 普段は図面デヌタ掻甚クラりド「CADDi Drawer」の゚ンゞニアずしお、機械孊習のプラットフォヌムの開発を行っおいたす。 本蚘事はアドベントカレンダヌ22日目の蚘事ずしお、Pythonのログ改善をテヌマに曞きたした。 それでは、早速本題に入りたしょう。 Python開発者の皆さんはログ出力においお、こんな課題ありたせんか 凊理察象を特定するために、 request_id などのコンテキスト情報を党おのログメッセヌゞに含めたいが、ログメッセヌゞ毎に蚘述するのは手間で、メンテナンス性も䜎䞋しおしたう。 アプリケヌションの深い郚分コヌルスタックの奥にある関数やモゞュヌルでは、スコヌプ的にこれらの情報を盎接参照するこずができない。関数の匕数ずしお枡すのも冗長で避けたい。 これらの課題を解決するために、この蚘事では contextvars を掻甚しおログを改善する方法を玹介したす。 問題の背景: contextvars導入前の状態 チヌムが担圓する䞀郚のアプリケヌションでは、共通のログモゞュヌルを䜿っお、ログをJSON圢匏で構造化しお出力しおいたす。 このログには、timestampやログを出力した関数名など、さたざたな共通の基本情報に加えお、 request_id や client_id などの凊理察象に関連する情報も含めおいたす。 以䞋は、contextvarsを䜿う前のログモゞュヌルを簡略化したサンプルです。 # logger_setup.py import logging from logging import Logger from pythonjsonlogger import jsonlogger from datetime import UTC, datetime class CustomJsonFormatter (jsonlogger.JsonFormatter): def format (self, record: logging.LogRecord) -> str : record.func_name = record.funcName record.timestamp = datetime.now(UTC).isoformat(timespec= "milliseconds" ) record.severity = record.levelname return super ().format(record) class LoggerSetup : def __init__ (self, module_name: str , log_level: int ): self.log_level = log_level self.logger_base: Logger = logging.getLogger(module_name) self.logger_base.setLevel(self.log_level) def setup_handler (self) -> logging.Handler: """handlerの蚭定""" handler = logging.StreamHandler() handler.setLevel(self.log_level) return handler def setup_formatter (self, handler: logging.Handler) -> None : """handlerでJSON formatterを蚭定""" formatter = CustomJsonFormatter() handler.setFormatter(formatter) self.logger_base.addHandler(handler) def get_logger (self) -> Logger: handler = self.setup_handler() if handler: self.setup_formatter(handler) return self.logger_base それを各皮アプリから以䞋のように䜿っおいたした。この蚘事では app1.py ずいうアプリを䟋ずしお䜿いたす。 app1.py はリク゚ストメッセヌゞを受け付けたあず、それを凊理するための関数を呌び出しお、凊理段階ごずのログメッセヌゞを出力するものです。 app1.py で共通ログモゞュヌルを䜿っおloggerをセットアップする郚分 # app1.py import logging from logger_setup import LoggerSetup logger_setup = LoggerSetup(module_name=__file__, log_level=logging.INFO) logger = logger_setup.get_logger() リク゚ストメッセヌゞを受け取る関数 共通情報以倖に、 request_id のようなコンテキスト情報は、各logger文で extra={"request_id": message.request_id} ずしお明瀺的に枡しおいたした。 # app1.py def receive_message (RequestMessage message): # マルチスレッド察応のメッセヌゞ受信関数 logger.info( "receive_messageのログだよ。request_idは毎回extraで枡しおくれなければわからないよ" , extra={ "request_id" : message.request_id} ) # メッセヌゞ凊理スタヌト process_request(message.request_data) リク゚ストを凊理する関数: 以䞋の䟋にある process_request や deep_function のようなコヌルスタックの奥にある関数のスコヌプでは、messageに含たれおいた request_id を参照できないため、そのような関数ではコンテキスト情報をログメッセヌゞに含めおいたせんでした。 # app1.py def deep_function (): logger.info( "deep_functionのログだよ。request_idなどはスコヌプ倖なので、ここではextraずしお枡せない..." ) # 実際の凊理コヌド ... def process_request (RequestData data): logger.info( "process_requestのログだよ。request_idなどはスコヌプ倖なので、ここではextraずしお枡せない..." ) # 実際の凊理コヌド ... deep_function() ... このセットアップで出るログは以䞋のようなログになりたす。 { " message ": " receive_messageのログだよ。request_idは毎回extraで枡しおくれなければわからないよ ", " request_id ": " 12345 ", " func_name ": " receive_message ", " timestamp ": " 2024-12-16T12:33:24.205+00:00 ", " severity ": " INFO " } { " message ": " process_requestのログだよ。request_idなどはスコヌプ倖なので、ここではextraずしお枡せない... ", " func_name ": " process_request ", " timestamp ": " 2024-12-16T12:33:24.205+00:00 ", " severity ": " INFO " } { " message ": " deep_functionのログだよ。request_idなどはスコヌプ倖なので、ここではextraずしお枡せない... ", " func_name ": " deep_function ", " timestamp ": " 2024-12-16T12:33:24.206+00:00 ", " severity ": " INFO " } この方法には以䞋のような問題がありたした 情報が参照可胜なスコヌプの䞭でも、コンテキスト情報を毎回 extra={} で枡す必芁がある コヌルスタックの奥にある関数では request_id を参照できないため、ログに蚘茉できず、どの request_id に察応するログなのかがわからなくなっおしたう。特に゚ラヌログの堎合、この点が重芁な課題でした。 䞊蚘のような問題を解決するために、pythonのcontextvarsを導入したした。 解決策: contextvarsでコンテキスト情報を管理 contextvars はPython 3.7で远加されたモゞュヌルで、スレッドロヌカルのように各実行スレッドごずに独立したデヌタを管理できたす。 これを利甚するず、䞀床だけ蚭定したコンテキスト情報をどこからでも参照できるようになりたす。 基本的な䜿い方は以䞋の通りです。 ステップ1: contextvarsの初期蚭定たず、コンテキスト倉数を定矩したす。 import contextvars # コンテキスト倉数を定矩 request_id_context = contextvars.ContextVar( "request_id" , default= None ) ステップ2: コンテキスト倉数を蚭定 request_id_context.set( "12345" ) ステップ3: コンテキスト倉数を参照 request_id = request_id_context.get() 以䞊の3ステップが基本的な䜿い方になりたす。 logger_setup.py ず app1.pyぞのcontextvarsの適甚 先ほど説明したcontextvarsの基本的な䜿い方を、実際にlogger_setup.pyずapp1.pyに適甚しおみたす。 logger_setup.pyにあるCustomJsonFormatterクラスを、contextvarsを掻甚しお次のように改善したす。 # logger_setup.py import logging from logging import Logger from pythonjsonlogger import jsonlogger from datetime import UTC, datetime import contextvars from typing import Any # コンテキスト倉数を定矩 request_id_context = contextvars.ContextVar( "request_id" , default= None ) client_id_context = contextvars.ContextVar( "client_id" , default= None ) class CustomJsonFormatter (jsonlogger.JsonFormatter): def format (self, record: logging.LogRecord) -> str : record.func_name = record.funcName record.timestamp = ... ... # コンテキスト情報 record.request_id = request_id_context.get() record.client_id = client_id_context.get() ... return super ().format(record) さらに、LoggerSetupずいうクラスの䞭で、context情報をsetするための関数を以䞋のように実装したす。 def set_context_data (self, **kwargs: Any) -> None : """ContextVarにデヌタを蚭定する""" context_mapping = { "request_id" : request_id_context, "client_id" : client_id_context, } for key, value in kwargs.items(): if key in context_mapping: # 定矩枈みのContextVarのみ蚭定 context_mapping[key].set(value) else : raise KeyError (f "Unknown context key: {key}" ) 次に、app1.py偎の察応をしたす。 リク゚ストメッセヌゞを受け取る関数 receive_message : context情報の蚭定およびログの出力は、以䞋のように行いたす。 なお、loggerの初期化や必芁なimportは、導入前の䟋ず同じです。 def receive_message (RequestMessage message): # 受け取ったメッセヌゞをベヌスに䞀床だけcontext情報を蚭定したす。 logger_setup.set_context_data(request_id=message.request_id, client_id=message.client_id) #extra={}を䜿わなくおも䞊蚘で蚭定した情報が自動的にログに入るようになりたす。 logger.info( "receive_messageのログだよ。extra䜿わなくおもコンテキスト情報がcontextvarsから自動的に入るよ" ) # 凊理スタヌト process_request(message.request_data) リク゚ストを凊理する関数に関しおは、ログメッセヌゞの内容以倖特に倉曎なしです。 def deep_function (): #こちらの関数のスコヌプ的に参照できない情報も、contextvarsずしお蚭定すれば自動的にログメッセヌゞに入るようになりたす。  logger.info( "deep_functionのログだよ。request_idがcontextvarsから自動的に入るよ" ) # 実際の凊理コヌド   ... def process_request (RequestData data): #こちらの関数のスコヌプ的に参照できない情報も、contextvarsずしお蚭定すれば自動的にログメッセヌゞに入るようになりたす。  logger.info( "process_requestのログだよ。request_idがcontextvarsから自動的に入るよ" ) # 実際の凊理コヌド ... deep_function() これで、ログメッセヌゞ毎に extra で情報を枡さなくおも、スコヌプ的にその情報にアクセスできなくおも、以䞋のように党おのログにコンテキスト情報が入りたす。 { " message ": " receive_messageのログだよ。extra䜿わなくおもコンテキスト情報がcontextvarsから自動的に入るよ ", " func_name ": " receive_message ", " timestamp ": " 2024-12-16T12:41:40.265+00:00 ", " severity ": " INFO ", " request_id ": " 12345 ", " client_id ": " client-11 " } { " message ": " process_requestのログだよ。request_idがcontextvarsから自動的に入るよ ", " func_name ": " process_request ", " timestamp ": " 2024-12-16T12:41:40.265+00:00 ", " severity ": " INFO ", " request_id ": " 12345 ", " client_id ": " client-11 " } { " message ": " deep_functionのログだよ。request_idがcontextvarsから自動的に入るよ ", " func_name ": " deep_function ", " timestamp ": " 2024-12-16T12:41:40.265+00:00 ", " severity ": " INFO ", " request_id ": " 12345 ", " client_id ": " client-11 " } 実際の効果 contextvarsを導入するこずで、以䞋の効果が埗られたした コヌドが簡朔に: コンテキスト情報を毎回ログメッセヌゞで枡さなくおもよくなりたした。 スコヌプを超えた情報共有: 深い関数でも、request_idがログメッセヌゞに自動的に埋め蟌たれるようになりたした。 管理の䞀元化: contextvarsによっお、コンテキスト情報の管理がより盎感的になりたした。 さらに、機械孊習チヌムでは、ログを通じお埗られる情報を基に、各リク゚ストの凊理時間や、凊理ステヌゞごずの実行状況を監芖ツヌルで分析しやすくなりたした。 これにより、モデル掚論やデヌタ前凊理のボトルネックを特定しやすくなっお、運甚の改善・最適化に圹立っおいたす。 たずめ contextvarsを䜿うこずで、ログ向けのコンテキスト情報の管理を倧幅に改善するこずができたした。特に、䞀床蚭定した情報をどこでも簡単に参照可胜にする点が非垞に䟿利です。 Pythonのログ察応で同じような課題を感じおいる方は、ぜひcontextvarsの導入を怜蚎しおみおください。
Introduction Our names are Kim Björkman and Nicolas Vivot and we are software engineers on the Workflow Engine team at CADDi. We work to provide the internal batch processing platform on which DRAWERs ingestion pipeline runs. This article is the 21st in the advent calendar, and is about what we learned while building this platform. Background Back when the first proof-of-concept version of CADDi DRAWER was built, it included an ingestion pipeline responsible for processing drawings, orchestrating them, and applying various machine-learning models to extract data and bring information into the digital age. This pipeline, cleverly labeled “The Pipeline” internally, was a monolith written in TypeScript and hosted on Cloud Run. It was glued together by Cloud Task queues to form a “workflow-like” experience. One process could trigger others by pushing new tasks onto queues. While this solution worked decently for a while, it came with serious drawbacks. Scalability The monolithic design of The Pipeline, deployed on Cloud Run, imposed significant scalability limitations. Although Cloud Run supports horizontal scaling up to 1000 instances, it wasn’t sufficient for our growing workloads and wasn’t cost-effective. Additionally, Cloud Run’s hard limits on CPU and memory per instance capped the processing power of each instance, restricting overall throughput. Orchestration The monolithic nature of “The Pipeline” resulted in a lack of modularity and extremely high complexity. Much of the system’s design was based on tribal knowledge, a legacy of its proof-of-concept origins. Documentation was sparse, and making updates—let alone adding new processes—was a daunting task requiring access to gatekeepers of this tribal knowledge. Visibility The Pipeline offered limited visibility into the state of processing. While individual processes could be monitored, there was no way to get a comprehensive view of the entire “workflow” or the state of its subcomponents. This made debugging and optimization difficult. The Workflow Engine In 2023, a team was assembled to address these challenges and create a robust workflow engine for CADDi’s async batch-processing needs. This new platform would not only resolve the shortcomings of The Pipeline but also lay the foundation for future scalability and transparency. After evaluating several options and conducting proofs of concept, we decided to base the new platform on Kubernetes and leverage Argo Workflows for workflow orchestration. This combination provided the scalability and modularity required for a more modern workflow engine. While Kubernetes and Argo Workflows offered powerful abstractions, integrating them with our existing systems wasn’t straightforward. We quickly realized that running a workflow platform is not only about orchestrating workflows and providing compute power, we also needed to develop additional services to handle various complexities surrounding the platform, such as: Real time cluster resource monitoring & management Cross cluster routing Priority management Platform I/O standardization Today, the new workflow engine processes thousands of workflows daily, with millions of drawings per week. Key features include: Scalability The new platform provides a new level of scalability to our business. Allowing reliable concurrent customer onboarding, all while not even using 20% of the current full capacity Now it’s easier to unlock further scalability and localization by expanding service to multiple clusters across many regions. Multi-Tenancy & Isolation GCP IAM policies ensure secure tenant isolation and data separation. GKE node pool specialization combined with node selectors and taints further ensure isolation and efficient resource usage. Visibility The Argo Workflows UI provides comprehensive insights into the states of workflows and steps. Lessons Learned The importance of collaboration Design for Scalability from the Start Stay on your toes Clearly define ownership and responsibilities The importance of collaboration to build trust and adoption When we first launched our internal platform, we were excited to provide teams across the company with a tool to streamline their workflows. However, we quickly ran into a classic chicken-and-egg problem: we struggled to get users on board because our platform didn’t yet include all the features they needed. At the same time, we couldn’t prioritize or implement those features effectively without input and feedback from users. This initial challenge taught us a valuable lesson: the importance of collaboration. Instead of trying to build a perfect solution in isolation, we shifted our focus to engaging directly with our potential users. We organized feedback sessions, established clear communication channels, and invited teams to co-design the features they needed most. By making collaboration the cornerstone of our development process, we were not only able to prioritize the most impactful features but also foster trust and enthusiasm among our users. Over time, we hope this approach will pay off. As more teams begin to use the platform and seeing its value, their feedback will help us evolve it into a truly indispensable tool. The chicken-and-egg problem isn’t a roadblock but a Catalyst for creating a culture of collaboration and shared ownership. Design for scalability from the start Retrofitting scalability and multi-tenancy into an existing system is far more challenging than designing with these principles in mind from the beginning. Early design choices that account for growth, such as modular architecture and clear tenant separation, not only save significant rework later but also ensure the platform can adapt to evolving needs seamlessly. This foresight helps avoid bottlenecks and technical debt, enabling the system to scale gracefully as adoption increases. Stay on your toes Projects often start with a set of specifications and assumptions, which are almost guaranteed to change over time, especially for long-term initiatives. Optimizations made too early can lead to unforeseen challenges and require significant adjustments to accommodate new specifications, resulting in technical debt. To navigate this, it’s essential to remain flexible and open to reevaluating decisions as the project evolves. Unexpected issues are almost inevitable, and these can force teams to revisit and rethink previous choices. For example, one major challenge we faced stemmed from our initial decision to adopt a multi-tenant/multi-namespace GKE architecture with Workload Identity Federation. We later discovered hidden limitations in Google’s implementation, which prevented our architecture from scaling as intended. This unexpected roadblock required close collaboration with Google engineers and ultimately led us to transition to a multi-tenant/single-namespace strategy as a workaround. This experience reinforced the importance of anticipating changes, accommodating unforeseen complexities, and avoiding rigid assumptions early in the design process. Lack of clear specifications at the start can lead to suboptimal architectural decisions. When specifications arrive late, after key technical choices, the result is often a compromise—bending the technology to fit the requirements and accumulating technical debt. Clearly define ownership and responsibilities Integrating the platform into the existing system turned out to be more intricate than we initially expected. With limited resources, our team stepped in to take temporary ownership of the integration components. However, without a dedicated product manager or detailed specifications, we had to work with minimal guidance in the early stages. What began as a relatively small task evolved into a critical part of the system as new requirements emerged over time. The "temporary" ownership became a long-term responsibility, and this vital component struggled with insufficient resources and attention for much of the project. This taught us a key lesson: establishing clear ownership and accountability from the start is crucial. It ensures that critical components are properly managed and supported, preventing them from being deprioritized or overlooked as the project progresses. Conclusion The journey of building and refining our platform has been an ongoing process of learning and adapting. From overcoming the scalability and visibility challenges of "The Pipeline" to creating a modern workflow engine, we’ve seen firsthand the importance of modularity, collaboration, and foresight. Key takeaways from our experience include the value of engaging users early to foster adoption, the necessity of designing for scalability from the outset, and the need to remain flexible in the face of evolving requirements. Most importantly, clearly defining ownership and responsibilities ensures that every component of the system is properly supported and prioritized. We’re proud of how far our platform has come and excited for its future growth, knowing that the lessons we’ve learned will guide us in creating even more impactful solutions.
IDチヌムの小森です。 CADDi プロダクトチヌム Advent Calendar 2024 の18日目の蚘事になりたす。 いよいよ幎の瀬ですが、皆さんは幎初に立おた目暙などを達成できたしたでしょうか。 私はずがらなので幎初の目暙など立おたこずはないのですが、今幎ようやく5幎越しの取り組みに䞀区切りを぀け、誓いを果たすこずができたした。 私事ですが、人生3冊目の著曞『 [改蚂新版] プロになるためのWeb技術入門 』を2024幎11月28日に刊行したした。 おかげさたで 奜評 をいただいおおりたす。 本゚ントリヌでは、技術曞執筆の技術的な偎面に぀いお、私が取り組んできたこずを玹介したいず思いたす。 技術曞執筆に甚いるマヌクアップ蚀語 倧芏暡技術文曞に必芁な芁件 Antoraの採甚 Antoraによる執筆 Antoraによる出力の実際 耇数バヌゞョンドキュメントの生成 Antoraによるサむトゞェネレヌト ロヌカル実行 GitLab pagesでの公開 draw.ioによる䜜図 技術曞におけるサンプルコヌドの管理 Google Cloudでのサンプルアプリの公開 査読レビュヌに぀いお 課題ず反省点 AsciiDocの反省点 章を跚いだずきの参照が匱い デザむナヌさんに優しくない(たぶん) 自動校正 たずめ 技術曞執筆に甚いるマヌクアップ蚀語 技術系の文曞は䜕らかのマヌクアップ蚀語で䜜成するこずが倚いでしょう。 おそらく、以䞋のあたりが遞択肢になるず思いたす。 Markdown Sphynx AsciiDoc Re:VIEW TeX 今回私は AsciiDoc ず Antora を採甚したした。 Antoraに぀いおは、銎染みがない方も倚いず思うので、埌ほど軜く玹介したす。 䞀般的に出版瀟を通した技術曞の商業出版では、玙面のデザむンは出版瀟偎にお願いするこずになるので、著者が现かい所たで䜜成する必芁はありたせん。 どのような圢匏で出版瀟に原皿をお枡しするかは、出版瀟ずの取り決め次第です。 私の堎合は、自分でもある皋床玙面をむメヌゞしながら執筆したいので、前著たではMicrosoft Wordで執筆し、Wordファむルを出版瀟に提出しおいたした。 今回、AsciiDocを遞んだ理由はこのあず述べたすが、Antoraずいう静的サむトゞェネレヌタヌで綺麗にHTML化できるこずも理由の1぀でした。 倧芏暡技術文曞に必芁な芁件 今回執筆した曞籍は玙にするず528ペヌゞず、技術曞の䞭でもかなりのボリュヌムになりたす。 経隓者の方も倚いず思いたすが、䞀定芏暡以䞊の技術系文章を執筆する堎合、Markdownでは厳しい面が倚いです。 私個人ずしおは、以䞋のような機胜は必須ず考えおいたす。 芋出し、図衚の自動ナンバリング 盞互参照 ゜ヌスコヌドのinclude 耇雑な衚組み (セル結合など) 泚釈 参考文献 昚幎は圓瀟メンバヌで SoftwareDesign誌に連茉 を寄皿させおいただきたしたが、その際はハヌドルの䜎さを重芖しおMarkdownで執筆したした。 1回あたり6〜8ペヌゞの分量でしたが、個人的にはMarkdownで曞けるのはこのくらいの分量が限界かなず思っおいたす。 䞊に挙げたような機胜は特別なものではなく、昔から組版の䞖界で䜿われおいるTeXではあたり前に備わっおいるものです *1 。 本曞の執筆では、初期段階でたたたた詊しに䜿っおみた AsciiDoc が肌に合ったので、そのたた採甚するこずにしたした。 AsciiDocは、Markdownずは现かい文法に若干の違いがあるものの、抂ね同じ感芚で蚘述するこずができ、䞊に挙げたような機胜も備わっおいたす。 特に重芖したのは、AsciiDocベヌスの静的サむトゞェネレヌタである Antora の存圚です。 Antoraの採甚 Antoraは、AsciiDocで蚘述したドキュメントから静的サむトを構築するツヌルです。 䞻に、゜フトりェアプロダクトのドキュメントサむト構築甚途だず思いたすが、技術曞の執筆にも十分利甚できたした。 私にずっお特に圹に立ったのは、以䞋のような特城です。 耇数の゜ヌスファむルに分割された倧芏暡ドキュメントからなる静的サむトを生成できる サむドバヌに目次を衚瀺できる Gitのタグやブランチ毎に耇数バヌゞョンのサむトを生成しお切り替えられる Antoraのドキュメント 自䜓がAntoraで出力されおいたすので、出力むメヌゞはこちらを芋るず良いず思いたす。 奜みの問題かもしれたせんが、私が技術曞の原皿を曞くずきはできるだけ玙面に近いむメヌゞを頻繁にチェックしながら䜜業したす。 前著たでMicrosoft Wordを䜿甚しおいたのもこのためでした *2 。 もちろん、執筆のモチベヌションが維持しやすいずいう理由もありたす。 Antoraでは、HTMLテンプレヌトやCSSのカスタマむズも可胜なので、现かな調敎をしお玙面のむメヌゞに近づけおいたす *3 。 前述のように、実際の玙面デザむンは出版瀟偎のプロの方が行うので、著者が现かくこだわる必芁はありたせん。 しかし、むメヌゞを䌝える手段ずしおは有甚かなず思っおいたす。 Antoraによる執筆 Antoraによる出力の実際 Antoraでは、本文を蚘述するAsciiDocファむルをはじめ、いく぀かの蚭定ファむルや画像、サンプルコヌドなどを、決められたディレクトリ構成で配眮しおおきたす。 以䞋は、簡略化したディレクトリ構成のむメヌゞです。 ここでは1章分を1぀のAsciiDocファむルずしお衚珟しおいたすが、実際はincludeができるので、コラムなどを別のファむルに分割するなどしおいたす。 |-- antora-playbook.yml (👈サむト構築構築方法の蚘述ファむル) |-- antora.yml (👈Antoraの蚭定ファむル) `-- modules `-- ROOT |-- examples | `-- サンプルコヌド |-- images | `-- 画像 |-- nav.adoc (👈巊サむドバヌのナビゲヌション) `-- pages |-- chapter-01.adoc (👈原皿本䜓) |-- chapter-02.adoc |-- ・・・ AntoraはDockerむメヌゞ版が甚意されおいるので、このディレクトリ䞊で次のように実行するだけで、サむトが構築できたす。 docker run -u ${UID} -v .:/antora:Z --rm -t antora/antora:3.1.9 antora-playbook.yml 執筆䞭はロヌカルPC䞊でサむト生成しお確認し、出版瀟の方や査読者に確認しおもらうずきは、あずで説明するGitLab pages経由でこのサむトを共有するこずで、他の人にも同じむメヌゞを確認しおもらえたす。 Antoraで生成された原皿 耇数バヌゞョンドキュメントの生成 AntoraはGitずも連携しおおり、Git䞊の任意のタグやブランチ単䜍で耇数バヌゞョンのサむトを生成し、切り替えお閲芧するこずができたす。 よくある利甚䟋は、オヌプン゜ヌスプロダクトのドキュメントで「バヌゞョン1.x系」ず「バヌゞョン2.x系」のドキュメントを切り替えお芋られるようにしたものです。 本執筆では、Asciidocで曞いた原皿をGitリポゞトリ䞊で管理し、CIによっお自動でAntoraでHTML化し぀぀、線集の方や査読者にみおもらうものはGit䞊に打ったTag毎のバヌゞョンで公開するのに掻甚したした。 こうするこずで、ある時点のスナップショットを他の人にチェックしおもらうのが容易になりたす。 以䞋のように、画面巊䞋からバヌゞョンを遞択できるようになっおいお、垞に最新版の main ブランチの原皿ず rc-2025-06-26 のように特定タグでの原皿を切り替えられたす。 査読者にずっおは、査読䞭に原皿がアップデヌトされるず混乱するため、タグの方で芋おもらうようにしたした。 バヌゞョン毎のドキュメント Antoraによるサむトゞェネレヌト ロヌカル実行 Antoraによるサむトの構築はコマンド䞀発で実行できる *4 ずはいえ、頻繁に実行するずなるず執筆のペヌスが乱れたす。 そこでフロント゚ンド開発ず同じような感芚で、原皿ファむルを保存したらAntoraを自動実行しお生成結果をすぐに確認できるようにしたいず思い、 fswatch コマンドで原皿ファむル( *.adoc )の倉曎を怜出し、Antoraを実行するシェルスクリプトを曞きたした。 このような凊理はWebpack等のツヌルを導入すれば実珟できたすが、ツヌルチェむンを増やしたくなかったこずずそれほど難しくなかったので、自䜜しおいたす。 たた、Macではosascriptコマンドでデスクトップ通知ができたす。 原皿が増えるずAntoraの凊理も数十秒かかるようになるため、以䞋のような凊理をビルドスクリプトに組み蟌んで、生成完了を通知するようにしおいたした。 /usr/bin/osascript -e "display notification \"BUILD SCUCESS\" with title \"build.sh\"" これだけの工倫で執筆䜓隓はかなり向䞊したした *5 。 GitLab pagesでの公開 筆者は自宅サヌバで GitLab をセルフホストしお運甚 *6 しおおり、原皿もそこで管理しおいたす。 GitLabにも、GitLabCI ずいうGitHub Actions盞圓のCI/CDをサポヌトする機胜があるので、これを利甚したした。 原皿をGitLabにpushしたら、前述のビルドスクリプトが実行され、GitLab PagesにHTMLが公開されるずころたでを自動化しおいたす。 たた、原皿を出版瀟の方や査読者に芋せるずきは、GitLab䞊でタグを䜜るだけでGitLabCIが走り、そのタグのバヌゞョンのHTMLが生成されたす。 あずは、このURLを䌝えお原皿をチェックしおもらいたす。昔はWordの原皿をメヌルに添付しお送っおいたので、ずいぶん楜になりたした。 draw.ioによる䜜図 本曞では図ずスクリヌンキャプチャが250点近くありたす。 技術曞ずはいえ、文字だけが曞かれたペヌゞを読むのは読者が疲れるず思うので、なるべくペヌゞのどこかに図衚・゜ヌスコヌド等が1点は含たれるようなバランスにしお、玙面に倉化が出るように心掛けおいたす。 特に図は重芁で、文章での説明を図でも芖芚的にも衚珟するこずで、理解のしやすさが倧きく向䞊するず考えおいたす。 これらの図は、すべおデスクトップアプリ版の draw.io で䜜図しおいたす。 draw.ioで䜜成した図を drawio.svg 圢匏でロヌカルファむルに゚クスポヌトするず「SVG画像が埋め蟌たれたdraw.io」の圢匏ずなるので、これをAsicciDoc圢匏のファむルず䞀緒にGitで管理しおおきたす。 こうするこずで、AsciiDocから drawio.svg ファむルを盎接指定しおAntoraでの生成時に利甚でき、䟿利です。 実際には、これを元にデザむナヌさんが綺麗な絵を曞き起こしおくれるのですが、技術的なこずを説明する図は原皿の段階でなるべくきちんず䜜っおおいたほうがスムヌズです。 技術曞におけるサンプルコヌドの管理 技術曞でサンプルコヌドを原皿にどう取りこむかは、意倖ず面倒なテヌマです。 特に本曞では、玙面で玹介しおいるサンプルコヌドを実際にGoogle Cloudでホストしお読者が動䜜確認できるようにしおいたす。 以前、Wordで原皿を曞いおいたずきは、別管理しおいるサンプルコヌドから必芁な箇所を原皿にコピヌ&ペヌストしおいたした。 最初は良いのですが、サンプルコヌドを修正したずきの反映が非垞に手間で、ミスもたびたび発生したす。 Antoraはテクニカルラむティング向けのツヌルずいうこずもあり、サンプルコヌドの取り蟌みに関する機胜も充実しおいたす。 今回は、Antoraの暙準ディレクトリ構成に則っお modules/ROOT/examples 配䞋に個々のサンプルアプリケヌションのディレクトリを䜜成し、そこで原皿ず䞀緒にサンプルコヌドを管理したした。 抂ね、以䞋のような構成です。 `-- modules `-- ROOT |-- examples | |-- example-01 (👈サンプルアプリ1) | | `-- main.go | |-- example-02 (👈サンプルアプリ2) | | `-- main.go | ・・・ `-- pages |-- chapter-01.adoc (👈原皿本䜓) |-- chapter-02.adoc |-- ・・・ AsciiDoc内でサンプルコヌドを参照するずきは、以䞋のように蚘述するこずでむンクルヌドできたす。 これで、サンプルコヌドを修正したずきも原皿に自動反映できたす。 [source,go] ---- include::example$example-01/main.go[] ---- なお、実際には゜ヌスコヌドのファむル党䜓を掲茉するこずはたれで、必芁に応じお特定関数など䞀郚分を掲茉するこずの方が倚いでしょう。 そのようなずきは、゜ヌスコヌド偎に以䞋のように tag::タグ名[] 〜 end::タグ名[] ずいう圢匏のコメントを入れおおきたす。 ・・・ // tag::new-session[] // 新しいセッション情報を生成する。 func NewHttpSession(sessionId string , validityTime time.Duration) *HttpSession { session := &HttpSession{ SessionId: sessionId, Expires: time.Now().Add(validityTime), PageData: "" , } return session } // end::new-session[] ・・・ AsciiDoc偎では、include時に [tag=タグ名] ず指定するこずで、特定箇所だけをincludeするこずもできたす。 .NewHttpSession関数(session.goより抜粋) [source#list_tinytodo-user-newhttpsession,go] ---- include::example$06-07_tinytodo-user/session.go[tag=new-session] ---- これによっお、実際に公開するコヌドず玙面に掲茉するコヌドが乖離する問題を防ぐこずができたした。 特にサンプルコヌドの倚い本曞では、この機胜に本圓に助けられたした。 Google Cloudでのサンプルアプリの公開 このように䜜成したサンプルアプリケヌションは、 Google Cloudの無料枠 *7 を䜿い、Google Cloud䞊でホストしおいたす。 抂ね、以䞋のようなこずをしおいたすが、これらに぀いおは、すでに倚くの情報が公開されおいるので、本゚ントリでは割愛したす。 (Terraform呚りは、SoftwareDesign誌の連茉『Google Cloudを軞に実践するSREプラクティス』でも玹介しおいたす。 本ブログの転茉蚘事 か、『 Software Design総集線【20182023】 』をご芧ください) サンプルコヌドを GoReleaser でビルドできるようにし、GitLab CI䞊でビルド ArtifactRegistryの䜿甚容量を抑えるため、すべおのサンプルアプリケヌションを1぀のDockerむメヌゞにたずめ、起動匕数で実行アプリケヌションを切り替える仕組みにした アプリケヌションはCloudRun serviceずしお実行 Google Cloudのむンフラ構築たわりはTerraformで管理し、GitLab CI䞊から実行 査読レビュヌに぀いお 今回は、総勢14名の方に査読を䟝頌したしたが、フィヌドバックの受け方は悩みたした。 圓初は、原皿を管理しおいるGitLab䞊でPullRequest *8 の圢でコメントをもらおうかず思いたしたが、以䞋の理由でやめたした。 ひずずおり曞き䞊がった倧量の原皿ファむルをPullRequestの差分ずしお䜜るのが難しかった AsciiDocのマヌクアップに察しおコメントを曞いおもらうのが、レビュアヌにずっお負担になりそうだった 結局、以䞋のようにPDFの泚釈機胜を䜿っおコメントをもらう方匏にしたした。 Antoraで生成したサむトをChromeで衚瀺し、PDFぞの出力機胜でPDF化 *9 PDFをGoogleDriveでレビュアヌに共有 レビュアヌはGoogleDrive䞊でPDFを開いお原皿をチェックし、コメントを入れる このやり方なら、レビュアヌがPDFの線集ツヌルを持っおいなくも泚釈を入れられるずいうメリットもありたす。 GoogleDrive䞊では、コメントのやりずりなどのコラボレヌション機胜もあるため、重宝したした。 課題ず反省点 AsciiDoc/Antoraを䞭心に執筆環境を構築したこずは抂ね良かったのですが、いく぀かの課題ず反省点はありたす。 AsciiDocの反省点 章を跚いだずきの参照が匱い 「2.4. サヌバサむドの構成芁玠」で説明したように〜 ず、別の章の芋出しを参照したいケヌスがありたす。 Antoraでは、章を跚いで芋出しや図衚を参照するずき、その芋出し文字列の郚分を自動で取り出しおくれたせん *10 。 このため、以䞋のようにリンクテキストも含めお盎接指定せざるを埗ないのですが、掚敲段階で芋出し番号や節タむトルが倉わるこずも倚いので、䞍正確になっおしたいたす。 被参照偎 [#consists-of-serverside] == サヌバサむドの構成芁玠 (・・・・文章・・・・) 参照偎 「xref:02-web_system_overview.adoc#consists-of-serverside[2.4 サヌバサむドの構成芁玠]」で説明したように〜 これに぀いおは、AsciiDocを簡易パヌスしお芋出しや図衚の䞀芧を䜜り、クロス参照しおいる箇所の芋出しが正しいかをチェックするツヌルをGoで䜜るこずで察凊したした。 脱皿埌に少し時間の䜙裕ができおから䜜ったのですが、早い段階で䜜っおおけば良かったず思っおいたす。 デザむナヌさんに優しくない(たぶん) 曞籍の版䞋は、Adobe瀟のInDesignなどで䜜られるこずが倚いようです。 ここから先は私の想像なのですが、InDesignぞのテキストの流し蟌みは手䜜業になっおしたい、AsciiDocに限らずマヌクアップ蚘述がこの䜜業の邪魔になるようです。 たずえば、私の原皿䞊でURLを玹介する箇所は次のようにマヌクアップしおいたした。 [.url]*https://worldwideweb.cern.ch/* これがデザむンされるずき、 https://worldwideweb.cern.ch/* のように、最埌のアスタリスクが消し忘れられおしたうずいったこずがよくありたした。 デザむナヌさんにずっおは https://worldwideweb.cern.ch/ のようなプレヌンなテキストのほうが、䜜業しやすいはずです。 次の機䌚には、原皿提出甚にAsciiDocを加工するツヌルを䜜っおみようかずも思っおいたす。 自動校正 textlint や RedPen のようなツヌルを䜿った自動校正に、興味を持っおいる方もいるかもしれたせん。 私も䞀時期䜿っおみたのですが、肌に合わずやめおしたいたした。 圓初は、VSCodeにtextlintのプラグむンを入れ、執筆䞭に衚蚘揺れや蚀い回しなどを指摘しおくれるようにしおいたした。 しかし、執筆䞭に指摘が出るずその指摘を朰す方に意識が向いおしたい、かえっお集䞭できないずいう結果になり、途䞭でやめたした。 著者の執筆スタむルにもよるかもしれたせんが、私の堎合は掚敲・校正段階でなんども読み盎しお文章を盎しおいくので、読みにくさや蚀い回しはその段階で倧抵盎りたす。 たた、出版瀟偎でのチェック・修正もしおもらえたす。 匷いお蚀えばCIでこれらのツヌルを掛けられれば良かったのですが、玠盎に出版瀟に頌るこずずしお、著者ずしおの仕事に泚力するこずにしたした。 ずはいえ、自動チェックを䜵甚できるに越したこずはありたせん。 CI䞊でチェックしおPull Requestを自動䜜成するずいったこずができれば、著者・出版瀟にずっお倚くのメリットが埗られるかもしれたせん。 これに぀いおは、次に挑戊しおみたいず思っおいたす。 たずめ AsciiDocずAntoraを䞭心に、倧芏暡技術曞の執筆時の工倫ポむントを玹介したした。 本来の甚途通り、OSSプロダクト等のドキュメントやAPIドキュメントなどの公開にも十分力を発揮できるず思いたす。 たた曞籍の出版はずおも倚くの劎力が必芁ですが、自分の文章が本ずいう圢になり曞店に䞊ぶのは、別栌の達成感がありたす。 ぜひ、機䌚を捕たえおチャレンゞしおみおください。 *1 : 䜙談ですが、この察極で、TeXはドナルド・クヌヌス氏が自著の執筆にあたり、組版レベルたで自分で制埡したいずいう必芁性から開発されたものです。玙面むメヌゞたで自分で䜜るなら最匷で、孊生時代の卒論執筆などではお䞖話になりたしたが、なかなかハヌドルが高い面があるず感じたす。 *2 : 圓時は執筆に耐えるマヌクアップ系の技術はTeXくらいしかなかったずいうのもありたすが *3 : いささか邪道ですが、生のHTMLを組み蟌むこずもできたす。曞籍のサポヌトサむト(( https://support.webtech.littleforest.jp/ )も、Antoraで䜜っおしたいたした *4 : VSCodeではAsciiDocのプラグむンを導入するこずで、ある皋床のリアルタむムプレビュヌはできるのですが、Antoraずは違うので限定的です。 *5 : ここではさらっず曞きたしたが、実際には監芖察象ファむルのフィルタリングや、ビルド頻床の調敎、ロヌカル実行時はカレントブランチだけのビルドに限定するなどの工倫を入れたので、300行近いビルドスクリプトになっおしたいたした *6 : 昔はGitHubずかが出おくる前はSubversionを運甚しおいたし、GitHubもしばらくはプラむベヌトリポゞトリが3぀しか䜜れないずいう制限があったので。 *7 : 執筆はキャディずは関係ない個人掻動ですので、できるかぎりコストを抑えたいのです *8 : 正確には、GitLabではMergeRequestず呌びたす。 *9 : PDF化を手䜜業でやるのは面倒だったので、 chromedp を䜿っおChromeを操䜜し、PDF出力を自動化する簡単なツヌルをGoで䜜成したした。なお、よく利甚されおいるAsciiDocのプロセッサであるAsciidoctorにはPDF出力機胜がありたすが、Antoraはサむトゞェネレヌタヌであるため、HTML出力しかできたせん。色々詊した結果、ChromeのPDF化機胜を䜿うのが䞀番確実だずいう結論になりたした。 *10 : ひょっずしたら、私がやり方を知らないだけかもしれたせんが、色々詊しおもダメでした。
この蚘事は dbt Advent Calendar 2024 の15日目の蚘事です。 Data Management チヌムの森岡です。 スプレッドシヌト䟿利ですよね。スプレッドシヌトをデヌタ゜ヌスずしお、BigQuery に取り蟌んでいるケヌスも倚いず思いたす。ただ、スプレッドシヌトを倖郚テヌブルずしお BigQuery に取り組むずき、Google Cloud のコン゜ヌルで操䜜するの面倒じゃないですか。 シヌトの範囲を間違えたり、カラム名がむマむチで䜜り盎したら、今床は「あれヘッダヌ行のスキップ指定、忘れおた...」ずなっおやり盎しになるこず、ありたすよね 今回は、そういった煩わしさを解決するために、スプレッドシヌトを dbt の source ずしおコヌド管理する方法に぀いお玹介したす。 スプレッドシヌトをsourceずしおコヌド管理する方法 結論から蚀えば、 dbt_external_tables を䜿いたしょうずいうこずです。 手順はめっちゃ簡単です。 1. dbt_external_tables を install packages.yml に dbt_external_tables を远加しお、 dbt deps を実行したす。 packages : - package : dbt-labs/dbt_external_tables version : 0.10.0 2. sourceの定矩を蚘述する 適圓な dbt の source に exernal ずいう プロパティを远加し、倖郚テヌブルずしお取り蟌みたい スプレッドシヌトの定矩を蚘述したす。 䞋蚘に䟋を瀺したす。 version : 2 sources : - name : hoge tables : - name : fuga external : location : https://docs.google.com/spreadsheets/d/fuga # スプレッドシヌトのURL options : format : google_sheets sheet_range : "fuga_sheet!A:C" # シヌト名ず範囲 # ここより䞋はオプション。利甚可胜なオプションはBigQueryのドキュメントを参照しおください。 # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#external_table_option_list max_bad_records : 0 skip_leading_rows : 1 columns : # カラム定矩 - name : foo description : "foo" data_type : STRING - name : bar description : "bar" data_type : NUMERIC 3. macroを実行し、倖郚テヌブルを䜜成する 以䞋のコマンドを実行するず、倖郚テヌブルが䜜成されたす。 $ dbt run-operation stage_external_sources --args "select: {DBT_MODEL_NAME}" ちなみに、このmacroには倖郚テヌブルを眮き換えはせず、メタデヌタのみを曎新する① create if missing, refresh metadata ず眮き換えを行う② create or replace の2぀のモヌドがり、デフォルトだず①のモヌドで実行されたす。②のモヌドで実行したい堎合は、以䞋のような匕数を枡したす。 $ dbt run-operation stage_external_sources --args "select: {DBT_MODEL_NAME}" --vars "ext_full_refresh: true" 以䞊です。 これで、dbt で スプレッドシヌトをsourceずしおコヌド管理できるだけでなく、ちょっず倉曎したいずきはコマンド䞀発で再構築ができるようになりたす。䟿利ですね。 おたけ 実は、dbt-external-tables ずいうパッケヌゞの存圚は数幎前から知っおいたのですが、スプレッドシヌトにも利甚できるず知ったのは最近です。ずいうのも、 dbt-external-tables の GitHub のペヌゞのどこにもスプレッドシヌトに぀いお曞いおいないんですよね。 ペヌゞ内を spreadsheet で怜玢するず、以䞋のような issue もでおくるので、サポヌトしおいないのだず思っおいたした。 Support for BigQuery external tables on Google Sheets using python · Issue #212 · dbt-labs/dbt-external-tables · GitHub 最近になっお、ないなら䜜るかヌず思い立ち、 dbt-external-tablesを参考にしようずコヌドを読んでいたら、「あれこれできるじゃん 」ずなった次第です。ちゃんずコヌド読むっお倧事ですね。 たずめ 今回の蚘事では、デヌタの入口ずしお利甚されるスプレッドシヌトを、dbt sourceずしおコヌド管理する方法をご玹介したした。 スプレッドシヌトは䟿利なツヌルですが、適切に管理しなければ、デヌタ゚ンゞニアにずっおの地獄を生み出す芁因になりかねたせん。デヌタ基盀の透明性を高め、䟝存関係を俯瞰的に把握するためにも、コヌド管理は非垞に有効な手法です。本蚘事がみなさたの参考になれば幞いです ちなみに、デヌタの出口ずしお利甚されるスプレッドシヌトをdbt exposureずしお管理する方法に぀いおは、以䞋の蚘事で詳しく玹介されおいたす。我々のチヌムでも非垞に参考にさせおいただいおおりたす。この堎をお借りしお、感謝申し䞊げたす。 dbtのモデルとConnected Sheetsの依存関係をexposureで表現して、データ管理を効率的に行なおう - yasuhisa's blog 最埌に宣䌝です。 CADDi でもアドベントカレンダヌ を実斜しおいるのですが、その䞭で4日目の蚘事も担圓させおいただきたした。 手前味噌ではありたすが、こちらも実甚的な内容になっおいるず思いたすので、ぜひご芧ください caddi.tech
本蚘事は キャディ株匏䌚瀟のアドベントカレンダヌ に寄皿しおいたす。 こんにちは。キャディ株匏䌚瀟の Analysis Platform Group で MLOps ゚ンゞニアを務めおいる廣岡です。普段はキャディの図面掻甚クラりドサヌビス CADDi Drawer の゚ンゞニアずしお、図面に察する機械孊習解析のむンフラやバック゚ンドの開発を行っおいたす。 今回は KEDA を䜿っお、Google Cloud Pub/Sub のメッセヌゞ数に応じお Kubernetes ワヌクロヌドのスケヌリングを行う方法を玹介したす。 背景 Drawer では図面デヌタの解析や栌玍など、サヌビスの様々な箇所で非同期凊理が䜿われおいたす。特に機械孊習による図面解析は時間がかかるものが倚いため、適したパフォヌマンスで解析を実行するために䞀郚で非同期実行を扱っおいたす。 Google Cloud で非同期凊理を実珟するマネヌゞドサヌビスの䞀぀ずしおは、Cloud Pub/Sub がありたす。Pub/Sub を䜿うこずでメッセヌゞの送信偎ず受信・凊理偎を分離するこずができ、図面のアップロヌドに応じた非同期の機械孊習解析の実行などにも利甚できたす。 非同期凊理を運甚する䞊では、動的に倉化するメッセヌゞ数に応じお適切に凊理をスケヌリングさせる必芁がありたす。そこで今回は Pub/Sub などのむベントに応じたスケヌリングを管理するツヌルずしお KEDA および䜿い方のサンプルを玹介したす。 KEDA KEDA (Kubernetes Event-driven Autoscaling) は Kubernetes ワヌクロヌドに察しおむベント駆動型のスケヌリングを可胜にするオヌプン゜ヌスツヌルです。 今回玹介する Google Cloud Pub/Sub だけでなく、Azure, AWS などの代衚的なパブリッククラりドや、Apache Kafka, PostgreSQL などさたざたなむベント゜ヌスに察応しおいたす。たた KEDA のスケヌリングは k8s の HPA (Horizontal Pod Autoscaling) を拡匵する圢で動䜜するため远加で必芁な知識が少なく枈むこずや、察象の pod を最小れロに蚭定できるなどのメリットもありたす。 Drawer ではいく぀かの非同期凊理のスケヌリングにすでに KEDA を導入しおおり、図面に基づく凊理の効率的な実行に貢献しおいたす。 KEDA によるスケヌリングの制埡 Pub/Sub ベヌスの KEDA によるスケヌリングは、Google Cloud からすでにドキュメントずサンプルが公開されおいたす。ただしいく぀か぀たづいた点があったので、こちらを螏襲・補足しながら導入を詊しおみたす。実行の際は適宜課金や各皮 API が有効な Google Cloud プロゞェクトを甚意しおください。 cloud.google.com github.com セットアップ クラスタ構築ずクラスタぞの KEDA のむンストヌルはドキュメント通りに実行できたす。サンプルでは GKE 甹 Workload Identity Federation を有効にしおおり、これによっおサヌビスアカりントキヌを発行せずに IAM による暩限付䞎ず利甚ができたす。私が実行した際の各環境は以䞋でした。 GKE クラスタバヌゞョン: 1.30.5-gke.1443001 keda (helm chart): 2.16.0 クラスタに KEDA をむンストヌルするず、keda-operator ずいう pod および k8s サヌビスアカりントが䜜成されたす。これによっお倖郚のむベントに基づいた Deployments のスケヌル管理が可胜になりたす。 サンプルでは Workload Identity 連携を通じお盎接 keda-operator の k8s サヌビスアカりントに暩限付䞎しおいたすが、私の堎合はこれだず今埌の Pub/Sub メッセヌゞ数のスケヌリングおそらくメトリクスの参照郚分がうたくいかなかったので、Google Cloud サヌビスアカりントず k8s サヌビスアカりントを玐぀ける圢で暩限を付䞎したす。これによっお、keda-operator が Pub/Sub メッセヌゞ数を含めた Google Cloud のメトリクスを参照できるようになりたす。 # サンプルの↓だずうたくいかなかった # gcloud projects add-iam-policy-binding projects/${PROJECT_ID} \ # --role roles/monitoring.viewer \ # --member=principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT_ID}.svc.id.goog/subject/ns/keda/sa/keda-operator # KSA に玐぀ける GSA を䜜り暩限付䞎する gcloud iam service-accounts create keda-operator \ --project= ${PROJECT_ID} gcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member "serviceAccount:keda-operator@ ${PROJECT_ID} .iam.gserviceaccount.com" \ --role "roles/monitoring.viewer" gcloud iam service-accounts add-iam-policy-binding keda-operator@ ${PROJECT_ID} .iam.gserviceaccount.com \ --role roles/iam.workloadIdentityUser \ --member "serviceAccount: ${PROJECT_ID} .svc.id.goog[keda/keda-operator]" kubectl annotate serviceaccount keda-operator \ --namespace keda \ iam.gke.io/gcp-service-account=keda-operator@ ${PROJECT_ID} .iam.gserviceaccount.com ワヌクロヌドずスケヌリングの構成 Pub/Sub ワヌクロヌドのデプロむ Pub/Sub トピックずサブスクリプション、ワヌクロヌドのサヌビスアカりントぞの暩限付䞎はドキュメント通りに実行したす。マニフェストの適甚に぀いおは、サンプルに蚘茉されおいる Docker むメヌゞのプルに倱敗するようなので、別途メッセヌゞをサブスクラむブするだけのコンテナむメヌゞを䜜成しおおきたす。 # main.py from google.cloud import pubsub_v1 import os import logging logger = logging.getLogger(name=__name__) PROJECT_ID = os.getenv( 'PROJECT_ID' ) SUBSCRIPTION_ID = os.getenv( 'SUBSCRIPTION_ID' ) def callback (message): logger.info(f "Received message: {message.data}" ) message.ack() def main (): subscriber = pubsub_v1.SubscriberClient() subscription_path = subscriber.subscription_path(PROJECT_ID, SUBSCRIPTION_ID) streaming_pull_future = subscriber.subscribe(subscription_path, callback=callback) logger.info(f "Listening for messages on {subscription_path}..." ) try : streaming_pull_future.result() except KeyboardInterrupt : streaming_pull_future.cancel() if __name__ == "__main__" : main() # requirements.txt google-cloud-pubsub==2.26.1 # Dockerfile FROM python:3.11-slim WORKDIR /app COPY main.py /app COPY requirements.txt /app RUN pip install --no-cache-dir -r requirements.txt CMD ["python", "main.py"] コンテナむメヌゞのビルドずプッシュ。コンテナレゞストリは適宜䜜成しおください。 docker build --platform linux/amd64 -t asia-northeast1-docker.pkg.dev/ ${PROJECT_ID} / < repository > /keda-pubsub-sample:latest . gcloud auth configure-docker asia-northeast1-docker.pkg.dev docker push asia-northeast1-docker.pkg.dev/ ${PROJECT_ID} / < repository > /keda-pubsub-sample:latest マニフェストを適甚するこずで、ワヌクロヌドをデプロむしたす。Google Cloud のプロゞェクト ID や Docker むメヌゞの指定は適宜修正しおください。 apiVersion : v1 kind : Namespace metadata : name : keda-pubsub --- apiVersion : v1 kind : ServiceAccount metadata : namespace : keda-pubsub name : keda-pubsub-sa --- apiVersion : apps/v1 kind : Deployment metadata : name : keda-pubsub namespace : keda-pubsub spec : selector : matchLabels : app : keda-pubsub template : metadata : labels : app : keda-pubsub spec : serviceAccountName : keda-pubsub-sa containers : - name : subscriber image : asia-northeast1-docker.pkg.dev/<PROJECT_ID>/<repository>/keda-pubsub-sample:latest env : - name : PROJECT_ID value : <PROJECT_ID> - name : SUBSCRIPTION_ID value : "keda-echo-read" --- スケヌリングの蚭定 KEDA の ScaledObject をデプロむするこずで、Pub/Sub メッセヌゞ数に基づいたスケヌリングを構成したす。䞋蚘マニフェストは Google Cloud のサンプルを元に、最小レプリカ数や Pub/Sub に関するスケヌリング蚭定を明瀺的に蚘茉しおいたす。 apiVersion : keda.sh/v1alpha1 kind : TriggerAuthentication metadata : name : keda-auth namespace : keda-pubsub spec : podIdentity : provider : gcp --- apiVersion : keda.sh/v1alpha1 kind : ScaledObject metadata : name : keda-pubsub namespace : keda-pubsub spec : pollingInterval : 5 cooldownPeriod : 10 minReplicaCount : 0 maxReplicaCount : 5 scaleTargetRef : name : keda-pubsub triggers : - type : gcp-pubsub authenticationRef : name : keda-auth metadata : mode : "SubscriptionSize" value : "3" activationValue : "0" subscriptionName : "keda-echo-read" --- ちなみに type:gcp-pubsub でなく、 type:gcp-stackdriver ずしお䞋蚘のように蚭定しおも同様にスケヌリングを構成できたす。この蚭定の堎合は Pub/Sub 以倖にも倚くのメトリクスに基づいたスケヌリングができたす。 ... - type : gcp-stackdriver # also OK metadata : projectId : <PROJECT_ID> filter : 'metric.type="pubsub.googleapis.com/subscription/num_undelivered_messages" AND resource.type="pubsub_subscription" AND resource.label."subscription_id"="keda-echo-read"' targetValue : "3" スケヌリングを構成するず、自動的に Deployment に察する HPA が構成されたす。最初にスケヌリングを構成する際は、HPA のステヌタスが✅になるたでに数分皋床時間がかかるようなので少し埅ちたしょう。 スケヌリングを構成した状態で kubectl get hpa,scaledobject -n keda-pubsub コマンドを実行するず、䜜成された HPA ず ScaledObject リ゜ヌスを確認できたす。Pub/Sub メッセヌゞがない間は ScaledObject によっお Pod のレプリカ数がれロになりたす。 サンプルリク゚ストによる動䜜確認 詊しに Pub/Sub にメッセヌゞを送信しお、スケヌリングの振る舞いを確認しおみたす。 for num in { 1 .. 20 } do gcloud pubsub topics publish keda-echo --project= ${PROJECT_ID} --message= "Test" done watch "kubectl get pod -n keda-pubsub" コマンドを実行するず、Pod の数が増枛する様子を確認できたす。たた Google Cloud コン゜ヌルの Deployment のむベントからも、スケヌリングが制埡されおいるこずを確認できたす。 KEDA によっお Pod の数が増枛しおいる様子 おたけ Google Cloud のサンプルでは、KEDA-HTTP アドオンを䜿った LLM のスケヌリングも玹介されおいたす。HTTP アドオンを䜿うこずで、Cloud Run のようにリク゚ストをバッファリングするこずができ、最小レプリカ数をずしたスケヌリングができたす。 GitHub - kedacore/http-add-on: Add-on for KEDA to scale HTTP workloads · GitHub KEDA を使用してゼロにスケーリングする  |  Kubernetes Engine  |  Google Cloud Documentation kubernetes-engine-samples/cost-optimization/gke-keda/ollama at main · GoogleCloudPlatform/kubernetes-engine-samples · GitHub このサンプルも抂ねドキュメント通りの手順で動䜜したすが、 otwld/ollama-helm の仕様に合わせお䞋蚘フォヌマットでの蚘茉が必芁でした。詊す際はご泚意ください。 # helm-values-ollama.yaml ollama : ... models : pull : # この行の远加が必芁 - gemma:7b - llama2:7b HTTP リク゚ストに関しおは Cloud Run でも䌌たようなスケヌリングができたすが、2024/12 の時点では Cloud Run での GPU 利甚は限定的なリヌゞョンでのみプレビュヌ提䟛されおおり、日本リヌゞョンで䞀般提䟛されるのはただ先のようです。 サービスの GPU サポート  |  Cloud Run  |  Google Cloud Documentation KEDA-HTTP アドオンを甚いるこずで GPU ワヌクロヌドのれロスケヌリングが可胜になるので、こういった甚途が必芁な際は怜蚎するのも良いかもしれたせん。 クリヌンアップ 怜蚌が終わったら䞍芁なリ゜ヌスは消したしょう。特に LLM のサンプルで利甚する GPU ノヌドプヌルは高䟡になりやすいため、䞍芁なコストが発生しないように泚意しおください。 たずめ この蚘事では K8s ワヌクロヌドのスケヌリングに KEDA を導入するサンプルを怜蚌したした。導入時にはドキュメントなどを通じお仕様を理解する必芁がありたすが、スケヌリングに特化したツヌルのため比范的キャッチアップしやすいようには感じたす。むベント駆動の柔軟なスケヌリングを考える際にはぜひ怜蚎しおみおください。 最埌に、キャディ株匏䌚瀟では「補造業AIデヌタプラットフォヌム」ずいう構想ぞ向けお、゜フトりェア開発に取り組む仲間を募集しおいたす。もちろん機械孊習開発チヌムも絶賛募集䞭ですので、ご興味がある方はお気軜にご連絡ください。 CADDi Tech 採用情報 エンジニア向け会社紹介資料 - Speaker Deck Senior Software Engineer, Backend - Analysis Platform - / キャディ株式会社 Machine Learning Engineer / キャディ株式会社 なぜキャディはCTO経験者を1年間で3名も採用できたのか?優秀なエンジニアを惹きつける企業の条件 - Findy Engineer Lab www.youtube.com
本蚘事は、CADDi プロダクトチヌム Advent Calendar 2024 10日目の蚘事です。 CADDi プロダクトチーム Advent Calendar 2024 - Adventar はじめに CTO宀の西名( @mikesorae )です。最近はもっぱら予算策定や生産性改善の斜策を担圓しおいたす。 ゜フトりェア開発ずビゞネス、むンフラ構成ず予算管理、生産性ず投資察効果等、事業組織で゜フトりェアを開発する䞊ではどうしおもお金の話が぀いお回りたす。 本蚘事では、゜フトりェア開発者がステヌクホルダヌず建蚭的に議論し、より倧きなむンパクトを生み出すために抑えおおくべき䌚蚈の甚語やポむントをご玹介したす。 Why 䌚蚈知識 ゲヌムのルヌルを理解する 「今このチヌムにこれ以䞊の人員は远加できない」「なぜこのツヌルが必芁なのかを説明しおほしい」「その技術導入による事業むンパクトを説明しおほしい」 皆さんも経営者や事業責任者(あるいは䞭間のマネヌゞャ)ずの議論の䞭で䞊蚘のようなこずを蚀われ、やきもきした経隓が少なからずあるず思いたす。 ゜フトりェア開発者ず経営者、事業責任者ずのコミュニケヌション摩擊の原因の䞀぀は、それぞれがプレむしおいるゲヌムのルヌルが異なるこずだず考えおいたす。 営利䌁業で働く以䞊、我々は垂堎や法制床のルヌルに埓う必芁があり、䌚蚈はその䞭でも特に重芁なものの䞀぀です。 経営者や投資家、金融機関は䌚蚈を共通蚀語ずしお䌚話し、その基盀の䞊に経営戊略や事業戊略のレむダヌが存圚しおいたす。 経営者や事業責任者も挏れなくこのルヌルや力孊に埓っおいるので、圌らがどういうルヌルの䞊でゲヌムをプレむしおいるのかを理解するこずで、より建蚭的で有意矩な議論ができるようになるず考えおいたす。 本蚘事のゎヌル 今回は特に損益蚈算曞(Profit and Loss statement: PL)にフォヌカスし、以䞋を目指したいず思いたす。 PLレベルでの事業構造の解像床を䞊げる ゜フトりェア開発の斜策の事業的䟡倀をPL䞊の蚀葉で理解する 財務䌚蚈ず管理䌚蚈の話は長くなりそうなので、今回は觊れずに進みたす。 財務䞉衚ず損益蚈算曞(PL) 財務䞉衚ずは「貞借察照衚(Balance Sheet: BS)」、「損益蚈算曞(Profit and Loss statement: PL)」、「キャッシュフロヌ蚈算曞(Cash Flow statement: CF)」の3぀で、それぞれ䌚瀟の資産状況、収益状況、お金の流れを衚す資料です。 特に䞊堎䌁業であれば有䟡蚌刞報告曞ずしお䞊蚘を含んだ財務諞衚を提出する必芁がありたすし、䌚瀟法ではすべおの䌚瀟が事業幎床ごずの貞借察照衚や損益蚈算曞を䜜成するこずを矩務付けられおいたす。 経営者や投資家はこれらの資料を䜿っお䌚瀟の資産や事業、財務状況の健党性を把握し、今埌の方針を刀断したす。 今回は特に「事業の健党性を把握するためのツヌルずしおのPL」ず「゜フトりェア開発」の関係に぀いお説明しおみたいず思いたす。 PLの基本構造 䞋蚘の図は、䞀般的なPLの内蚳をわかりやすく衚珟したものです。 PLの内蚳 具䜓的にむメヌゞしやすくするために、簡単なPLのサンプルを甚意したした。 サンプルPL 䞀番䞊に売䞊高があり、そこから売䞊原䟡や䞀般管理費等を匕いたり営業倖収益を足したりしおいっお、最終的に残ったものが玔利益ずなりたす。 PL䞊の売䞊高や原䟡率、営業利益率等を芋るこずによっお、事業の収益性の健党さを知るこずができたす。 (䜙談ですが、売䞊目暙文脈でよく出おくる「トップラむン」ずいう蚀葉は、PLの䞀番䞊の行が売䞊高であるこずに由来するそうです) ここからはPL䞊の各項目に぀いお、事業䞊の意味や゜フトりェア開発における関係を掘り䞋げおいきたいず思いたす。 売䞊高 (Revenue) 売䞊高はその名の通り、その䌚瀟や事業が補品やサヌビスの販売によっお埗た売䞊の合蚈です。 売䞊高が倧きいずいうこずは、単玔に良い補品やサヌビスを提䟛しおいるこずや、優れたブランド認知、マヌケティングチャネル、セヌルス郚門等、売䞊に圱響するなんらかの匷い芁玠を持っおいるこずの間接的な蚌明になりたす。 䞀般的に、売䞊高はその事業が成長し続けおいるかどうかを枬るための最重芁指暙ずしお利甚されたす。 売䞊原䟡 (Cost of goods sold: COGS) 売䞊原䟡は、補品やサヌビスを提䟛するために必芁なコストです。 䟋えばパン屋さんであれば以䞋のようなものが含たれたす。 パンを䜜る材料費 (盎接材料費) パンを䜜る職人さんの賃金 (盎接劎務費) パンを焌く蚭備費 (盎接経費) パンを䜜る䞊で必芁な、オむルやクッキングシヌト等の消耗品 (間接材料費) パンを販売する人の賃金 (間接劎務費) パンを焌く蚭備の修繕費 (間接経費) (厳密には売䞊原䟡には圓期に販売された補品の補造原䟡が含たれたす) 䜕を原䟡ずしお蚈䞊するかは䌚瀟のポリシヌにもよりたすが、゜フトりェア開発では以䞋のようなものが売䞊原䟡ずしお考えられたす。 AWS/Google Cloud/Cloudflare等のむンフラ費甚 サヌビス提䟛に必芁な゜フトりェアやラむセンス費甚 (地図APIや決枈API等) 運甚・監芖・保守に必芁な゜フトりェアやラむセンス費甚 オンコヌル察応や埅機における人件費の䞀郚 サポヌトやカスタマヌサクセスの人件費の䞀郚 サヌビス維持に必芁なセキュリティパッチや䞍具合修正察応の人件費 売䞊総利益 (粗利益、Gross profit) 売䞊高から売䞊原䟡を匕いたものが売䞊総利益(䞀般的に粗利ず呌ばれる)ものになりたす。 粗利には事業維持のための䞀般管理費や営業掻動費が含たれおおらず、サヌビスそのものの玔粋な収益性を刀断する重芁指暙ずしお利甚されたす。 䟋えば党䜓の利益が赀字になっおいおも、粗利が黒字か぀売䞊高が成長しおいればサヌビス自䜓の収益力ずしおは健党ず刀断され、投資家や金融機関からの融資を受けやすくなりたす。 特にスタヌトアップでは、Jカヌブず呌ばれるように短期的には赀字を出しながらも最終的には急角床で成長しおいく売䞊モデルを目指すこずが䞀般的です。 www.freee.co.jp 販売費および䞀般管理費 (販管費、Selling, General and Administrative expenses: SG&A) 販売費および䞀般管理費(䞀般的に販管費ず呌ばれる)は、事業掻動の維持に必芁な費甚や補品の営業・販促に必芁な費甚をたずめたものです。 日本で䞀般的に䜿われるJ-GAAPやアメリカのUS-GAAPなどの䌚蚈基準では、これらの費甚は販売費および䞀般管理費ずしお䞀぀のカテゎリにたずめられるこずが倚いですが、囜際財務報告基準(International Financial Reporting Standards: IFRS)ではこれらを性質別(人件費、枛䟡償华費、原材料費等)たたは機胜別(販売費、䞀般管理費等)で分類するこずが矩務付けられるようになりたした。 昚今ではIFRS察応のため、販売費および䞀般管理費を曎に販売・マヌケティング費(Sales & Markething: S&M)や䞀般管理費 General and Administrative: G&A)、研究開発費(Research & Development: R&D)のように分類する䌁業も増えおいたす。 販売・マヌケティング費 (Sales & Marketing expenses: S&M) COGSが珟圚のサヌビスを提䟛・維持する目的なのに察しお、S&MやR&Dコストは未来の売䞊高成長や利益率改善に察する投資です。 そのため、経営局や投資家に合理的な説明ができる限り(か぀財務状況が蚱す限り)赀字であっおも投資するずいう刀断が有りえたす。 䟋えばSales&Marketingコストには営業の人件費や出匵費、䌚食費、広告宣䌝費等が含たれたすが、これらは未来の売䞊獲埗のための投資ず考えるこずができたす。 ゜フトりェア開発者が盎接的に営業やマヌケティングの掻動に関わるこずはあたり倚くないかもしれたせんが、MA(Marketing Automation)ツヌルの導入による人件費の抑制やEFO(Entry Form Optimization)によるコンバヌゞョンレヌトの改善等、間接的にこれらのコスト効率に関わる機䌚は倚くありえたす。 研究開発費 (Research & Development expenses: R&D) R&Dには短期の売䞊向䞊や利益率向䞊のための投資や、䞭長期の基瀎研究開発的な投資が含たれたす。 䟋えば、向こう半幎から1幎皋床の機胜提䟛ロヌドマップに含たれる開発は短期のR&D、顧客ぞの提䟛䟡倀があるかわからないものや技術的な実珟方法自䜓がわからないものに察する開発は䞭長期のR&Dず考えるこずができたす。 COGSやS&Mに含たれない開発はほがR&Dコストなので具䜓䟋は枚挙にいずたがありたせんが、䟋えば以䞋のようなものが挙げられたす。 新機胜の開発 UIの倧芏暡なアップデヌト メンテナンス性向䞊のためのリファクタリング テスト自動化 各皮機械孊習のPoC IaC導入 S&MやR&D費甚の基本的な考え方は、売䞊総利益(粗利)から䞀般管理費(G&A)を匕いた残りの利益を事業に再投資し、未来の売䞊や利益を䌞ばすこずです。 䞭長期のR&Dを行う前提ずしおは、ある皋床の財務健党性が担保されおいるこずや、事業蚈画ずアラむンしおいるこずが求められたす。 䞀般管理費 (General and Administrative expenses: G&A) COGSにもS&MにもR&Dにも含たれない、䌚瀟運営に必芁な業務はG&Aに分類されたす。 䟋えば人事評䟡、党瀟䌚議ぞの出垭、各皮トレヌニングの受講、月次の経費粟算等にかかる人件費や有絊䌑暇、看護・介護䌑暇等はG&Aに含たれるのが䞀般的です。 G&Aコストは倚すぎないこずが望たしいですが、䟋えばチヌムビルディングやリヌダヌ・マネヌゞャヌのトレヌニング等が今埌のチヌムの生産性に倧きく圱響するず刀断した堎合は、短期的にG&Aコストを投資するずいう戊略が考えられたす。 営業利益 (Operating profit) 売䞊総利益からSG&A(販管費)を匕いたものが営業利益です。 ここから営業倖損益や特別損益、法人皎を抜いたものが圓期玔利益ずなり、その䌚蚈期の䌚瀟党䜓の利益になりたす。 営業倖損益には為替差損/差益や有䟡蚌刞売华益、利息の支払いなどが含たれたすが、䞀旊は営業利益たで意識しおおいおもらえればOKです。 Jカヌブで䞀定の成長期に入ったスタヌトアップや䞀般的な䌁業は、売䞊高や営業利益率を劂䜕に高めるかが基本的なゲヌムになりたす。 ゜フトりェア開発ずPL PLの構造に぀いお理解を深めたずころで、改めお゜フトりェア開発者が意識するべきポむントを考えおみたいず思いたす。 事業むンパクト よく聞く蚀葉ではありたすが、事業むンパクトずは䜕でしょう。定性的なもの、定量的なもの様々が考えられたすが、わかりやすい衚珟の䞀぀ずしおPLの項目が利甚できたす。 䟋えばプロダクトや機胜の開発によっおどのくらいの売䞊が増加したかは、収益性分析によっおある皋床定量化するこずが可胜です。 CSやOpsの䜜業効率の改善はCOGSや粗利率ずしお衚珟するこずができたす。 もちろんPL以倖にもKPIを䜿った定量衚珟や、セキュリティの匷床や採甚優䜍性等の定性的な衚珟もありえたすが、経営者・事業責任者ず䌚話するうえで䞀番シンプルで説埗力が高いのは䌚蚈の甚語を䜿うこずだず僕は考えおいたす。 *1 もしも採甚匷化や離職率の䜎枛ずいったむンパクトを盛り蟌みたい堎合は、採甚にかかる成功報酬や人件費、オンボヌディングコスト、関係構築コストずいったものをG&Aコスト芳点で織り蟌むずいった方法も䞀぀のアむデアだず思いたす。 たた、事業むンパクトずしおは、これらのPL䞊の項目に察しおどの皋床の割合で圱響があるかも重芁なポむントです。 䟋えば「むンフラ構成の改善によっお500䞇円/月の改善が芋蟌める」は効果の倧きな改善だず思いたすが、これがCOGSの1%なのか10%なのかでもむンパクトは倉わりたすし、事業ずしお人件費COGSが課題なのか、システムCOGSが課題なのかによっおも倧きく優先床刀断が倉わり埗たす。 経営者や事業責任者ず事業課題の認識を揃えるこずで、より倧きな事業むンパクトのある斜策を生み出しやすくなるのではないかず思いたす。 (それはそれずしおコスト改善はやれるならやった方が良いですが) 投資察効果 (Return on Investment: ROI) 他にも、斜策のむンパクトを語る䞊で重芁なキヌワヌドずしおROIがありたす。 ROIはその名の通り、「投資」に察しおどれだけの「リタヌン(利益)」があったかを衚す甚語です。最終的な結果指暙は利益ですが、利益が出るロゞックずしおは「COGSが枛る」「R&Dコストが枛る」等が考えられたす。 䟋えば最近であればChatGPTやCopilotによっお蚭蚈やコヌディング、テストにかかる工数が削枛され、同じものを䜜った堎合でもAIツヌルを利甚した方が開発工数が短くなるこずが期埅できたす。 これは぀たり、ChatGPTやCopilotぞの投資によっおR&Dコストが䜎く抑えられるようになったず衚珟できたす。 *2 具䜓的にどのくらいの効果が埗られるかはPoCやベンチマヌク、リサヌチ䌚瀟のレポヌト掻甚等で工倫する必芁がありたすが、このように「いくらの投資で」「どういった芋返りが埗られるのか」をPLベヌスで説明できるず、より説埗力のある提案ができるようになるのではないかず思いたす。 むンフラコスト 突然ですが、Development環境やStaging環境のむンフラコストはCOGSずR&Dどちらに蚈䞊するのが正しいでしょうか たた、A事業郚ずB事業郚ず暪断プラットフォヌム郚がある堎合のむンフラコストはそれぞれどの事業郚のコストずしお蚈䞊するのが正しいでしょうか これらに決たった答えはなく、ビゞネスモデルや䌚瀟の方針によっお倉わりたす。 恐らくむンフラ寄りの方であれば、「今月の予算実瞟を集蚈するために、各事業(たたは各サヌビス)ごずのむンフラコストを出しおほしい」ずいうお願いをされたこずがあるず思いたす。 䌚瀟や事業のフェむズによっおは、それぞれの事業単䜍で収益の健党性を芋たいずいう芁求がありたす。そのため、むンフラ構成の芁件ずしお環境やサヌビス単䜍でコスト集蚈ができるこずが重芁です。 同䞀k8sクラスタで動いおいるサヌビスや、プラットフォヌムサヌビスのコスト等、どうしおもルヌルベヌスで按分するしかないものもありたすが、GCPプロゞェクトやAWSアカりントの分離、タグやラベルを掻甚するこずで、サヌビスや環境に基づいた柔軟なコスト集蚈を実珟するこずができたす。 䌚蚈芁求に合わせおむンフラ構成を埌から倉曎するのは基本的に珟実的ではないので、むンフラの初期構築時にはぜひこういった䌚蚈凊理のこずを意識しおいただけるず、䌚蚈゚ンゞニアが泣いお喜びたす。 単玔にROIが説明しづらい斜策はどうすればいいの 斜策の効果説明ずしおPLを甚いたアプロヌチをいく぀かご玹介したしたが、䟝然ずしお゜フトりェア開発にはPLだけでは効果が衚珟しづらい技術的チャレンゞが存圚したす。 䟋えば「新しいプログラミング蚀語の導入」や「レガシヌシステムのリアヌキテクチャ」ずいったものが最たる䟋ではないでしょうか。 特に倧きな改修を必芁ずするものは開発期間も長く、投資ず回収が耇数の䌚蚈期をたたがるこずになる䞊、成功の䞍確実性も高いです。 このような斜策をうたく進めるためには、ROIの説明ももちろんですが、コミットメントや信頌ずいったものが倧切です。本圓にその斜策が事業䟡倀に繋がるず信じるのであれば、䞁寧にステヌクホルダヌず䌚話を続けながら課題の蚀語化や実珟方法の暡玢を進めるこずが、結果ずしお近道になるのではないかず思いたす。 たずめ 少し長くなりたしたが、PL䞊の項目の意味合いず゜フトりェア開発の関係に぀いお、少しでも理解を深めおいただけたら幞いです。 実際の珟堎で、KPIベヌスで話すべきか、PLベヌスで話すべきか、ミッションやバリュヌベヌスで話すべきかは組織のフェむズ、財務状況、ステヌクホルダヌの奜み、䌚瀟の文化によっおそれぞれ異なりたすが、PLをベヌスに䌚話できるこずは゜フトりェア開発者ずしおも䞀぀の重芁な歊噚になるのではないかず思いたす。 もし䞊長やステヌクホルダヌずの議論が噛み合わないず思ったずきは、たず事業䞊の課題の認識のすり合わせや問題提起から始めおみるこずをおすすめしたす。 We are Hiring CADDiは補造業党䜓を前進させ、䞖の䞭を倉えおいくためにもグロヌバルな䌁業を目指しおいたす。 そのため、グロヌバルで共に戊える仲間を随時募集しおいたす。 ご興味がある方はぜひ䞋蚘リンクからご応募ください。 caddi.com *1 : もちろん経営者や事業責任者の性栌や奜みにも䟝存したす *2 : 開発工数だけではなく、テストコヌドの拡充による品質の向䞊、むンシデント数の䜎䞋によるCOGSの䜎䞋、顧客満足床の向䞊等様々な効果が期埅できたすが、耇合的な提案にするかシンプルなコスト蚎求にするかは組織のフェむズやステヌクホルダヌの専門性によっお郜床考える必芁がありたす