TECH PLAY

電通総研

電通総研 の技術ブログ

822

みなさんこんにちは! 金融ソリューション事業部の市場系ソリューション1部の寺山です。12 月に入ってから一気に寒くなったように感じます。 本記事は 電通国際情報サービス Advent Calendar 2022 の 12 月 22 日の記事です。 昨年の アドベントカレンダー でテックブログに参加して以来、普段の業務においても「何か記事にできることはないかな?」と考えるようになり、一周り充実した1年でした! 今回は、 AWS Fargate で実行した Web アプリケーション(バックエンド API )のマルチスレッドが想定外の挙動となった点をインフラ視点で調査したので、発生した事象とその対応方法について紹介いたします! 発生した事象 概要 調査開始時の予想 原因 JavaのavailableProcessors cgroupのcpu.sharesパラメータ 対応 コンテナ定義のcpuパラメータの指定 XX:ActiveProcessorCountオプションの利用 非同期処理を検討する おわりに 発生した事象 概要 バックエンド API をホストする クラウド 構成は下図のとおりです。本件で触れないサービスは割愛しています。 バックエンド API はコンテナ化し、 AWS Fargate で ECS タスクとして起動します。 API リク エス トは Application Load Balancer が受け付け、ターゲットグループに登録した ECS サービスへルーティングする典型的な クラウド アーキテクチャ です。 バックエンド API は Java (11)で実装されています。 事象は、レスポンスの返却に時間を要するリク エス トの処理中に発生しました。 ALB のターゲットグループのヘルスチェックは、バックエンド API のヘルスチェック用エンドポイントを指定していました。このエンドポイントがヘルスチェックの タイムアウト 時間内に正常レスポンスを返却しないことを 閾値 の回数以上に繰り返したため、クライアントのリク エス ト処理中の Fargate タスクが Unhealthy と判定され、停止されてしまいました。 タスクの停止理由に、 Task failed ELB health checks in (target-group arn:aws:elasticloadbalancing:ap-northeast-1:XXXXXXXXXXXX:targetgroup/m5infr-sandb-D87C6CX3M8FL/a5285befd3edc05d) と出力されています。 調査開始時の予想 私は事象の初回発生時、当該システム/プロジェクトには関与しておらず、後に調査を引き取った形になります。 この事象の話を聞いた当初は CPU リソースの不足が理由だろうからタスクサイズをチューニングすればよい(バックエンド API のタスクサイズは vCPU:1 でした)ものだと考えていました。 実際に検証したところ、私の予想とは異なる結果となりました。まずはその調査結果を紹介します。 事象が発生したアプリケーションとは別にアプリチームが調査用のアプリケーションを用意してくれたので、それを同じ構成で AWS にデプロイし検証しました。 4vCPU を割り当てた Fargate タスクで再現するか検証します。 API リク エス トは、コンテナイメージの手動ビルド用途として作成した EC2 インスタンス で行いました。 $ curl http://<ALBのDNS名>:8080/sandbox/greet ; echo everyone! hello!everyone! /sandbox/greet はリク エス トに対し、即座に hello! と返却する API です。上記のように正常時は curl コマンドでレスポンスを取得できます。 $ (curl http://<ALBのDNS名>:8080/sandbox/sleep ; echo done at `date`) & [1] 3087 $ curl http://<ALBのDNS名>:8080/sandbox/greet ; echo everyone! <html> <head><title>502 Bad Gateway</title></head> <body> <center><h1>502 Bad Gateway</h1></center> </body> </html> <html> <head><title>502 Bad Gateway</title></head> <body> <center><h1>502 Bad Gateway</h1></center> </body> </html> everyone! 次は、 /sandbox/sleep エンドポイントをバックグラウンドジョブとして実行し、ジョブが終了した日付を表示するようにしています。 /sandbox/sleep は TimeUnit.sleep を無期限に実行する API です。応答に時間を要する API というのを簡易的に表現しています。 バックグラウンドジョブの後に /sandbox/greet を curl していますが、今度はレスポンスを取得できずに API コンテナが終了したため 502 エラーとなっています。 実際にタスクも停止されており、事象が再現しています。 sleep でアイドル状態になっているので自明ではあるのですが、リソース使用率も低いことがメトリクスより確認できます。 つまり、タスクサイズのチューニングにより解消するという私の予想には反して、本件はタスクサイズに依らず発生する事象であることがわかりました。 原因 Java のavailableProcessors バックエンド API は Java のマイクロサービス軽量 フレームワーク である Helidon を使用しています。 この フレームワーク ではデフォルトの動作として、スレッドプール数を Runtime.getRuntime().availableProcessors() の戻り値に設定します。 1 ECS/Fargate では availableProcessors が vCPU のサイズに依らず 1 となるため、すでにスレッドを使用中の場合に、リク エス トを処理するためのスレッドを新たに取得できないことが原因です。 実際に確認してみます。 確認は、 ECS Exec を利用して Fargate 内で CLI 操作をすることにより行いました。 対象のタスクは先ほどと同じ 4vCPU を割り当てています。 $ aws ecs describe-tasks --cluster m5infrasurvey-poc-ecs-cluster --tasks 6218b4dd29c146a6ac120c5395a6e462 { "tasks": [ { # 省略 "cpu": "4096", # 省略 "memory": "8192", # 省略 } ], "failures": [] } ECS Exec よりコンテナのシェルにアクセスし、 JShell より availableProcessors を実行します。 $ aws ecs execute-command --cluster m5infrasurvey-poc-ecs-cluster --task 6218b4dd29c146a6ac120c5395a6e462 --container sandbox --interactive --command "sh" The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Starting session with SessionId: ecs-execute-command-0dc2b2f03561dd7b9 # # jshell Dec 19, 2022 5:33:04 PM java.util.prefs.FileSystemPreferences$1 run INFO: Created user preferences directory. | Welcome to JShell -- Version 11.0.16.1 | For an introduction type: /help intro jshell> Runtime.getRuntime().availableProcessors() $1 ==> 1 jshell> /exit | Goodbye 戻り値が 1 であることを確認しました。 比較として、EC2 インスタンス として起動している Amazon Linux2 に Docker Server をインストールし、同じ Docker イメージを起動して確認してみます。 docker exec コマンドでシェルにアクセスして、上記同様に JShell を実行します。 $ docker exec -it sandbox "sh" # # lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 46 bits physical, 48 bits virtual CPU(s): 8 On-line CPU(s) list: 0-7 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 85 Model name: Intel(R) Xeon(R) Platinum 8124M CPU @ 3.00GHz Stepping: 4 CPU MHz: 3416.106 BogoMIPS: 5999.99 Hypervisor vendor: KVM Virtualization type: full L1d cache: 128 KiB L1i cache: 128 KiB L2 cache: 4 MiB L3 cache: 24.8 MiB NUMA node0 CPU(s): 0-7 Vulnerability Itlb multihit: KVM: Mitigation: VMX unsupported Vulnerability L1tf: Mitigation; PTE Inversion Vulnerability Mds: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Vulnerability Meltdown: Mitigation; PTI Vulnerability Mmio stale data: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Vulnerability Retbleed: Vulnerable Vulnerability Spec store bypass: Vulnerable Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Retpolines, STIBP disabled, RSB filling Vulnerability Srbds: Not affected Vulnerability Tsx async abort: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe 1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowpr efetch invpcid_single pti fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves ida arat pku ospke # # jshell Dec 20, 2022 9:07:44 AM java.util.prefs.FileSystemPreferences$1 run INFO: Created user preferences directory. | Welcome to JShell -- Version 11.0.16.1 | For an introduction type: /help intro jshell> Runtime.getRuntime().availableProcessors() $1 ==> 8 jshell> /exit | Goodbye インスタンス タイプは c5.2xlarge です。参考として上記では lscpu も実行しています。 コンテナ起動時に CPU オプションは指定していないため、ホストの CPU がそのまま見えています。 availableProcessors() は EC2 インスタンス の vCPU 数を返却しており、ECS/Fargate とは挙動が異なっています。 cgroupのcpu.sharesパラメータ コンテナ仮想化におけるリソース割り当てに利用されるcgroupのcpuサブシステムのsharesパラメータは、コンテナが 利用可能なCPUリソースのスケジューリングに利用されます。Dockerでは run コマンドの --cpu-shares パラメータで指定します。 割合は cpu.shares パラメータの値を 1024 で除算した値で計算されます。 Dockerの場合はこのパラメータにデフォルトで 1024 が適用されます。 https://docs.docker.jp/config/container/resource_constraints.html#cfs コンテナへの配分を定めるもので、デフォルト値は1024 です。 つまり、ホスト上で実行するコンテナが1台で他にリソース制限を付与するパラメータが指定されていない場合、割合は 1024/1024=1 となりホストの CPUリソースを全て利用可能となります。 一方、ECSではデフォルト値として cpu.shares に 2 が適用されます。 https://aws.amazon.com/jp/blogs/news/how-amazon-ecs-manages-cpu-and-memory-resources/ 注: コンテナに CPU ユニットを指定しない場合、ECS は内部的に cgroup に対して 2 ( Linux カーネル が許容する最小値) という値の Linux CPU 配分 (cpu.shares) を設定します。 Fargateタスクに対し、 Java の -Xlog:os+container=trace オプションを使用して JVM のリソース認識状況を出力したものが以下になります。 $ aws ecs execute-command --cluster m5infrasurvey-poc-ecs-cluster --task 83d8e044611d4d22bbeaf65d164af34f --container sandbox --interactive --command "/bin/sh" The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Starting session with SessionId: ecs-execute-command-0dc479f32fcd9891d # # java -Xlog:os+container=trace -version [0.000s][trace][os,container] OSContainer::init: Initializing Container Support [0.001s][debug][os,container] Detected cgroups hybrid or legacy hierarchy, using cgroups v1 controllers [0.001s][trace][os,container] Path to /memory.use_hierarchy is /sys/fs/cgroup/memory/memory.use_hierarchy [0.001s][trace][os,container] Use Hierarchy is: 1 [0.001s][trace][os,container] Path to /memory.limit_in_bytes is /sys/fs/cgroup/memory/memory.limit_in_bytes [0.001s][trace][os,container] Memory Limit is: 9223372036854771712 [0.001s][trace][os,container] Non-Hierarchical Memory Limit is: Unlimited [0.001s][trace][os,container] Path to /memory.stat is /sys/fs/cgroup/memory/memory.stat [0.001s][trace][os,container] Hierarchical Memory Limit is: 8589934592 [0.001s][info ][os,container] Memory Limit is: 8589934592 [0.001s][trace][os,container] Path to /cpu.cfs_quota_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us [0.001s][trace][os,container] CPU Quota is: -1 [0.001s][trace][os,container] Path to /cpu.cfs_period_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us [0.001s][trace][os,container] CPU Period is: 100000 [0.001s][trace][os,container] Path to /cpu.shares is /sys/fs/cgroup/cpu,cpuacct/cpu.shares [0.001s][trace][os,container] CPU Shares is: 2 [0.001s][trace][os,container] CPU Share count based on shares: 1 [0.001s][trace][os,container] OSContainer::active_processor_count: 1 [0.001s][trace][os,container] CgroupSubsystem::active_processor_count (cached): 1 [0.001s][trace][os,container] CgroupSubsystem::active_processor_count (cached): 1 # 省略 実際に CPU Share is: 2 と出力されていることが確認できました。 2/1024≒0.002 のためコンテナに割り当てられる CPUリソース数が極端に少なく、 JVM は利用可能な CPU 数の最小値である 1 を適用する挙動をしたと判明しました。ECS/FargateとDockerにて挙動の異なる点にも説明がつきます。 対応 コンテナ定義のcpuパラメータの指定 前のセクションで説明した通り、 cpu.shares がデフォルトでは 2 であることが、タスクサイズに依らず1スレッドしか利用できない事象の深層的な原因であるとわかりました。 ECSにおいて cpu.shares を指定するには、コンテナ定義の cpu パラメータを指定すれば良いです。タスク定義におけるタスクサイズの cpu とは別のパラメータであることに注意してください。 私の検証では環境構築に AWS CDK を利用したので、対応例として ソースコード の一部を紹介します。 利用しているバージョンは package.json の一部の紹介で代替いたします。 { //省略 " devDependencies ": { " @types/jest ": " ^29.2.3 ", " @types/node ": " ^18.11.9 ", " @typescript-eslint/eslint-plugin ": " ^5.20.0 ", " @typescript-eslint/parser ": " ^5.20.0 ", " aws-cdk ": " ^2.51.0 ", " eslint ": " ^8.13.0 ", " eslint-config-prettier ": " ^8.5.0 ", " jest ": " ^29.3.1 ", " prettier ": " ^2.6.2 ", " ts-jest ": " ^29.0.3 ", " ts-node ": " ^10.9.1 ", " typescript ": " ^4.9.3 " } , " dependencies ": { " aws-cdk-lib ": " ^2.51.0 ", " constructs ": " ^10.0.0 ", " source-map-support ": " ^0.5.16 " } } const sandboxTaskdef = new TaskDefinition ( this , 'sandboxTaskdef' , { compatibility: Compatibility.FARGATE , networkMode: NetworkMode.AWS_VPC , family: ` ${ SYSTEM_NAME } - ${ props.m5Props.m5Env } -ecs-taskdef-sandbox` , executionRole: sandboxTaskRole , taskRole: sandboxTaskRole , cpu: '4096' , memoryMiB: '8192' , } ); sandboxTaskdef.addContainer ( 'sandbox' , { image: ContainerImage.fromEcrRepository ( Repository.fromRepositoryArn ( this , 'sandbox' , 'arn:aws:ecr:ap-northeast-1:XXXXXXXXXXXX:repository/m5infrasurvey-poc-ecr-sandbox' ) ), containerName: 'sandbox' , cpu: 1024 , // cpu.sharesに設定される /*省略*/ } ); 宣言した TaskDefinition クラスの addContaier メソッドからコンテナ定義の cpu パラメータを指定しています。 上記のコードをデプロイして起動されたFargateタスクの cpu.shares を実際に確認してみます。 まずは起動されたタスクの情報です。 $ aws ecs describe-tasks --cluster m5infrasurvey-poc-ecs-cluster --tasks 46e1d6fd883c47419093ad0e84a41ad1 { "tasks": [ { # 省略 "containers": [ { "containerArn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:container/m5infrasurvey-poc-ecs-cluster/46e1d6fd883c4741 9093ad0e84a41ad1/342343c2-0d87-4cdf-b60a-4496307cfc33", "taskArn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task/m5infrasurvey-poc-ecs-cluster/46e1d6fd883c47419093ad0e84 a41ad1", "name": "sandbox", # 省略 "managedAgents": [ { "lastStartedAt": "2022-12-21T17:30:48.947000+09:00", "name": "ExecuteCommandAgent", "lastStatus": "RUNNING" } ], "cpu": "1024" } ], "cpu": "4096", # 省略 } ], "failures": [] } タスクサイズとは別にコンテナ定義として "cpu": "1024" が反映されていることを確認しました。 続いて、 JVM が認識するリソース情報と availableProcessors の戻り値を確認します。 $ aws ecs execute-command --cluster m5infrasurvey-poc-ecs-cluster --task 46e1d6fd883c47419093ad0e84a41ad1 --container sandbox --interactive --command "/bin/sh" The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Starting session with SessionId: ecs-execute-command-0d914bdaa48aec635 # # java -Xlog:os+container=trace -version [0.000s][trace][os,container] OSContainer::init: Initializing Container Support [0.001s][debug][os,container] Detected cgroups hybrid or legacy hierarchy, using cgroups v1 controllers [0.001s][trace][os,container] Path to /memory.use_hierarchy is /sys/fs/cgroup/memory/memory.use_hierarchy [0.001s][trace][os,container] Use Hierarchy is: 1 [0.001s][trace][os,container] Path to /memory.limit_in_bytes is /sys/fs/cgroup/memory/memory.limit_in_bytes [0.001s][trace][os,container] Memory Limit is: 9223372036854771712 [0.001s][trace][os,container] Non-Hierarchical Memory Limit is: Unlimited [0.001s][trace][os,container] Path to /memory.stat is /sys/fs/cgroup/memory/memory.stat [0.001s][trace][os,container] Hierarchical Memory Limit is: 8589934592 [0.001s][info ][os,container] Memory Limit is: 8589934592 [0.001s][trace][os,container] Path to /cpu.cfs_quota_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us [0.001s][trace][os,container] CPU Quota is: -1 [0.001s][trace][os,container] Path to /cpu.cfs_period_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us [0.001s][trace][os,container] CPU Period is: 100000 [0.001s][trace][os,container] Path to /cpu.shares is /sys/fs/cgroup/cpu,cpuacct/cpu.shares [0.001s][trace][os,container] CPU Shares is: 1024 [0.001s][trace][os,container] OSContainer::active_processor_count: 4 [0.001s][trace][os,container] CgroupSubsystem::active_processor_count (cached): 4 [0.001s][trace][os,container] CgroupSubsystem::active_processor_count (cached): 4 [0.037s][trace][os,container] Path to /cpu.cfs_quota_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us [0.038s][trace][os,container] CPU Quota is: -1 [0.038s][trace][os,container] Path to /cpu.cfs_period_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us [0.038s][trace][os,container] CPU Period is: 100000 [0.038s][trace][os,container] Path to /cpu.shares is /sys/fs/cgroup/cpu,cpuacct/cpu.shares [0.038s][trace][os,container] CPU Shares is: 1024 [0.038s][trace][os,container] OSContainer::active_processor_count: 4 # 省略 # # jshell Dec 21, 2022 5:55:00 PM java.util.prefs.FileSystemPreferences$1 run INFO: Created user preferences directory. | Welcome to JShell -- Version 11.0.16.1 | For an introduction type: /help intro jshell> Runtime.getRuntime().availableProcessors() $1 ==> 4 以下が確認できました。 CPU Shares is: 1024 と出力され、 cpu.shares が意図通りに設定されていること active_processor_count: 4 と出力され、タスクのvCPUと同値が認識されていること availableProcessors の戻り値が 4 と出力され、タスクのvCPUと同値であること 最後に、本件の事象が解消しているかを事象の再現方法と同じように確認します。 $ curl http://m5infrasurvey-poc-alb-sandbox-352400359.ap-northeast-1.elb.amazonaws.com:8080/sandbox/greet ; echo everyone! hello!everyone! $ $ (curl http://m5infrasurvey-poc-alb-sandbox-352400359.ap-northeast-1.elb.amazonaws.com:8080/sandbox/sleep ; echo done at `date`) & [1] 4454 $ (curl http://m5infrasurvey-poc-alb-sandbox-352400359.ap-northeast-1.elb.amazonaws.com:8080/sandbox/sleep ; echo done at `date`) & [2] 4457 $ (curl http://m5infrasurvey-poc-alb-sandbox-352400359.ap-northeast-1.elb.amazonaws.com:8080/sandbox/sleep ; echo done at `date`) & [3] 4460 $ $ curl http://m5infrasurvey-poc-alb-sandbox-352400359.ap-northeast-1.elb.amazonaws.com:8080/sandbox/greet ; echo everyone! hello!everyone! スレッドプール数は 4 になっているので、事象の再現に使用した /sandbox/sleep API を 3 リク エス ト受け付け後も、 /sandbox/greet API からレスポンスを取得できています。想定通りの結果を得ることができました。 XX:ActiveProcessorCountオプションの利用 前のセクションの対応で、 cpu.shares の設定方法がわかりました。 しかしながら、 18.0.2+ , 17.0.5+ , 11.0.17+ からOpenJDKにて cpu.shares を用いて利用可能なCPU数の制御を廃止する( 2 )ため、 Java のバージョン次第で前述した方法では事象が再現します。 そこで、 Java の拡張ランタイムオプションである -XX:ActiveProcessorCount=N を利用します。このパラメータを利用すると、 JVM が認識する CPU の数を上書きできます。 前述した対応ではコンテナのリソース量を環境に合わせて動的に割り当て可能ですが、こちらの場合は環境ごとにパラメータを変更する必要があります。 -XX:ActiveProcessorCount=N を利用するケースにおいても、対応例として AWS CDKの ソースコード の一部を紹介します。 const cpuNum = 4 ; const sandboxTaskdef = new TaskDefinition ( this , 'sandboxTaskdef' , { compatibility: Compatibility.FARGATE , networkMode: NetworkMode.AWS_VPC , family: ` ${ SYSTEM_NAME } - ${ props.m5Props.m5Env } -ecs-taskdef-sandbox` , executionRole: sandboxTaskRole , taskRole: sandboxTaskRole , cpu: String ( cpuNum * 1024 ), memoryMiB: '8192' , } ); sandboxTaskdef.addContainer ( 'sandbox' , { image: ContainerImage.fromEcrRepository ( Repository.fromRepositoryArn ( this , 'sandbox' , 'arn:aws:ecr:ap-northeast-1:XXXXXXXXXXXX:repository/m5infrasurvey-poc-ecr-sandbox' ) ), containerName: 'sandbox' , environment: { TZ: 'Asia/Tokyo' , JAVA_TOOL_OPTIONS: `-XX:ActiveProcessorCount= ${ cpuNum } ` , // 環境変数で-XX:ActiveProcessorCountを設定 } , } ); タスクに割り当てる vCPU の数をあらかじめ宣言しておき( cpuNum )、タスクサイズの cpu パラメータと -XX:ActiveProcessorCount= の値に使用します。 本番環境と開発環境間でタスクサイズは異なることが多いので、 -XX:ActiveProcessorCount= は JAVA_TOOL_OPTIONS 環境変数 を利用して外部から注入する形で付与するのがベターと考えました。 このように クラウド のサービスやリソースを宣言的に構築できる点や、値やオブジェクト等を再利用可能なところもIaCのメリットですね! 前のセクションと同じように反映されたか確認します。 $ aws ecs execute-command --cluster m5infrasurvey-poc-ecs-cluster --task e4696c0103574ad0a07a8eabc1360087 --container sandbox --interactive --command "/bin/sh" The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Starting session with SessionId: ecs-execute-command-0c5d73c5e08474f67 # # jshell Picked up JAVA_TOOL_OPTIONS: -XX:ActiveProcessorCount=4 Dec 20, 2022 6:58:07 PM java.util.prefs.FileSystemPreferences$1 run INFO: Created user preferences directory. | Welcome to JShell -- Version 11.0.16.1 | For an introduction type: /help intro jshell> Runtime.getRuntime().availableProcessors() $1 ==> 4 JAVA_TOOL_OPTIONS 環境変数 が適用されていること、および、 availableProcessors の戻り値も指定した値になっていることが確認できました。 実際に API にリク エス トを行う動作確認は前述と同じ結果になったため記載は割愛します。 非同期処理を検討する 前述までの対応は、タスクの vCPU を 2 以上に設定することが前提となっています。 常時多数のリク エス トを受けるつけるようなワークロードであればよいですが、それ以外の場合はリソースに余剰が発生しコスト最適化の設計原則に反しています。 理想は、必要最小限のタスクサイズとタスク数で ECS サービスを起動し、負荷に応じてスケールすることです。 今回の発端となったような応答まで時間を要するような処理は非同期で実行することで、スレッドを長時間占有することを回避するという案です。 AWS においては SQS と Lambda を利用してメッセージキューイングによる非同期処理をサーバレス アーキテクチャ で実現する方法が知られています。 上記の図の例では、バックエンド API は enqueue に成功した時点でレスポンスを返却するため、本件の事象を回避するという意図になります。 非同期化した場合は API クライアント側にポーリングやリフレッシュの仕組みが必要になるため、 API 仕様として許容可能かの調整も必要となる点には注意が必要です。 おわりに 今回は ECS/Fargate で発生した事象について紹介いたしました。 今後は他の クラウド プロバイダのコンテナ関連サービスにおける事象の発生有無についても調査したいと考えています。 最後までご覧いただきありがとうございました。皆さま良いお年を! 私たちは一緒に働いてくれる仲間を募集しています! 金融機関のようなお客様においても クラウド ファーストなアプローチやマイクロサービスのようなモダンな技術の採用が進みつつあります。ご自身のインフラ/ クラウド スキルを エンタープライズ 領域でも活用したい・挑戦したいという方がいらっしゃいましたら、ぜひご応募ください! 金融システム インフラ・アーキテクト 金融システム インフラエンジニア 執筆: 寺山 輝 (@terayama.akira) 、レビュー: @mizuno.kazuhiro ( Shodo で執筆されました ) https://github.com/helidon-io/helidon/blob/helidon-2.x/webserver/webserver/src/main/java/io/helidon/webserver/ServerConfiguration.java#L58-L65 , https://github.com/helidon-io/helidon/blob/helidon-3.x/common/configurable/src/main/java/io/helidon/common/configurable/ServerThreadPoolSupplier.java#L54-L68 ↩ https://bugs.openjdk.org/browse/JDK-8281181 ↩
アバター
本記事は Microsoft Azure Tech Advent Calendar 2022 の22日目の記事です X イノベーション 本部 AITC の後藤です。最近、Azureが提供する 機械学習 サービスであるAzure Machine Learning(Azure ML)の SDK v2が新たにGAになりました。 Azure MLは先日 TechPlayのイベント でもご紹介した、私たちのチームが背極的に活用している 機械学習 のさまざまな用途に活用可能なサービスです。 Azure ML SDK v2はv1と大きく使い方が変わりました。本記事ではまだまだ情報が少ないAzure ML SDK v2に関して公式ドキュメントをベースに使い方を紹介します。 そもそもAzure Machine Learning (Azure ML) とは Azure ML SDK v1とv2の比較 Azure ML SDK v2の基本的な使い方 準備 データセット登録 学習の実行 学習済みモデルの登録 デプロイ SDK v2の感想 おわりに そもそもAzure Machine Learning (Azure ML) とは Azure ML は、Azure の 機械学習 サービスです。一言で 機械学習 サービスといってもAzure ML は非常に多くの機能を提供しており、 機械学習 に関連する多くの ユースケース で活用可能です。 機械学習 を実際の業務で運用していくいわゆるMLOpsを実現するには多くの工程が必要です。モデルを学習させるまでも大変ですが、学習後に実際の業務で使用できるように運用していくのはさらに大変です。Azure MLではそういった 機械学習 をビジネスで活用しようとする際に遭遇する多くの問題を解決してくれます。 参考: https://learn.microsoft.com/ja-jp/azure/machine-learning/overview-what-is-azure-machine-learning Azure ML SDK v1とv2の比較 Azure ML SDK v2はv1から大きく変更が加えられ、使い方もほとんど原型がないレベルで変わっています。また、互換性に関してもv1とv2で維持されないものもあります。こういった理由から、v1とv2を同じコードベースで使用することは推奨されていません。 以下は SDK で使用されるクラスを念頭にv1とv2の概念の比較をした表です。 AzureML v1 AzureML v2 Workspace Workspace Datastore Datastore Compute Compute Webservice OnlineEndpoint、OnlineDeployment ParallelRunStep for Batch Scoring BatchEndpoint、BatchDeployment Experiment, Run, Pipeline Job Dataset FileDataset TabularDataset Data assets(v1との整合性が取れていない) URI _FILE URI _FOLDER MLTABLE Model Model Environment Environment 学習ジョブや推論まわりは名称含め大きく変わっていることがわかります。また、上記の表で名前が一緒でも、与える引数が異なるものなども存在します。 参考: https://learn.microsoft.com/ja-jp/azure/machine-learning/how-to-migrate-from-v1#using-v1-and-v2-code-together Azure ML SDK v2の基本的な使い方 それでは基本的な 機械学習 のフローとして以下の内容をAzure ML SDK v2で順番に実行し、使い方を紹介します。 環境準備 デー タセット 登録 学習の実行 学習済みモデルの登録 モデルのデプロイ 準備 Python パッケージの azure-ai-ml をインストールします(ちなみに SDK v1は azureml-core です)。次にAzure portal もしくは Azure CLI でAzure Machine Learning workspace(AML ワークスペース )を作成します。 ワークスペース を作成後、必要な情報をメモし、以下のように Python でMLClientを定義します。 from azure.ai.ml import MLClient from azure.identity import DefaultAzureCredential # AMLワークスペースの情報 subscription_id = "<subscription_id>" resource_group = "<リソースグループ名>" workspace = "<ワークスペース名>" ml_client = MLClient( DefaultAzureCredential(), subscription_id, resource_group, workspace ) SDK v2の主要な変更の一つとして、上記のMLClientが挙げられます。 SDK v1では、ExperimentやRunなど様々なクラスを用途に応じて使い分ける必要がありましたが、v2におけるAMLの操作は全てこのMLClientから実行します。 デー タセット 登録 Azure MLに学習に使用するデータを登録します。 ちなみに細かいですが、v1では データセット と呼称されていたものがv2では データ資産 という呼称に変更されています。本記事では一般的な 機械学習 用語として「デー タセット 」という言葉を使用しています。 参考: https://learn.microsoft.com/ja-jp/azure/machine-learning/how-to-migrate-from-v1#data-datasets-in-v1 Azure MLに登録する際のデータソースには以下の選択肢があります。 ローカルファイル Azure BLOB Storage Azure Blob Storage はAzure のオブジェクトストレージサービスです 参考: https://learn.microsoft.com/ja-jp/azure/storage/blobs/storage-blobs-overview Azure Data Lake Storage Gen2(ADLS gen2) ADLS gen2はAzure Blob Storage をベースに構築された、ビッグ データ分析用途のストレージサービスです Datastore Azure ML で指定するAzureのストレージサービスです 上記のBlobやADSK gen2を指定することで安全にAzure MLからデータにアクセスが可能になります 今回は単純にローカルファイルをAzure MLのデータ資産に登録します。デー タセット 登録は先ほどのMLClientを使用して以下のように書きます。 from azure.ai.ml.entities import Data from azure.ai.ml.constants import AssetTypes # データの場所によりpathの記述方法が異なる # local: './<path>' # blob: 'https://<account_name>.blob.core.windows.net/<container_name>/<path>' # ADLS gen2: 'abfss://<file_system>@<account_name>.dfs.core.windows.net/<path>/' # Datastore: 'azureml://datastores/<data_store_name>/paths/<path>' my_data = Data( path="./data/iris.csv", # ローカルファイルパス type=AssetTypes.URI_FILE, description="irisデータ", name="iris", version='1' # データセットのバージョン ) # AMLにデータセットを登録 ml_client.data.create_or_update(my_data) 参考: https://learn.microsoft.com/ja-jp/azure/machine-learning/how-to-create-data-assets?tabs=CLI#tabpanel_2_Python-SDK 正常に処理が終わるとブラウザでAML ワークスペース のアセット⇨データより以下のように確認できます。 学習の実行 デー タセット の登録が終わったので、次に学習を実行します。 Azure ML SDK v2における学習の実行場所はローカルかコンピュート クラスタ ーの2つです。コンピュート クラスタ ーはAMLにおける学習用の計算リソースです。コンピュート クラスタ ーは事前に作成しておく必要があります。作成は SDK や CLI 、AML ワークスペース からも可能です。 今回はAML ワークスペース からコンピュート クラスタ ーを作成します。AML ワークスペース より左のブレードメニューからコンピューティングを選択し、新規ボタンから作成できます。作成すると以下のようにコンピューティング クラスタ ータブに作成した クラスタ ーが表示されます。 コンピュート クラスタ ーはその名の通り設定した台数で VM が必要に応じてスケールしてくれます。最小の VM 台数を0に設定できるので、学習を実行する時だけ計算リソースを立ち上げることができます。逆に最大の台数を増やせばその分同時に実行できる学習も増やせます。 参考: https://learn.microsoft.com/ja-jp/azure/machine-learning/quickstart-create-resources 続いて、コンピュート クラスタ ー内で実行する学習 スクリプト を用意します。今回はMSのexampleのコードを拝借しました。(引用元: https://github.com/Azure/azureml-examples ) # imports import os import mlflow import argparse import pandas as pd import matplotlib.pyplot as plt from sklearn.svm import SVC from sklearn.model_selection import train_test_split # define functions def main(args): # enable auto logging mlflow.autolog() # setup parameters params = { "C": args.C, "kernel": args.kernel, "degree": args.degree, "gamma": args.gamma, "coef0": args.coef0, "shrinking": args.shrinking, "probability": args.probability, "tol": args.tol, "cache_size": args.cache_size, "class_weight": args.class_weight, "verbose": args.verbose, "max_iter": args.max_iter, "decision_function_shape": args.decision_function_shape, "break_ties": args.break_ties, "random_state": args.random_state, } # read in data df = pd.read_csv(args.iris_csv) # process data X_train, X_test, y_train, y_test = process_data(df, args.random_state) # train model model = train_model(params, X_train, X_test, y_train, y_test) def process_data(df, random_state): # split dataframe into X and y X = df.drop(["species"], axis=1) y = df["species"] # train/test split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=random_state ) # return splits and encoder return X_train, X_test, y_train, y_test def train_model(params, X_train, X_test, y_train, y_test): # train model model = SVC(**params) model = model.fit(X_train, y_train) # return model return model def parse_args(): # setup arg parser parser = argparse.ArgumentParser() # add arguments parser.add_argument("--iris-csv", type=str) parser.add_argument("--C", type=float, default=1.0) parser.add_argument("--kernel", type=str, default="rbf") parser.add_argument("--degree", type=int, default=3) parser.add_argument("--gamma", type=str, default="scale") parser.add_argument("--coef0", type=float, default=0) parser.add_argument("--shrinking", type=bool, default=False) parser.add_argument("--probability", type=bool, default=False) parser.add_argument("--tol", type=float, default=1e-3) parser.add_argument("--cache_size", type=float, default=1024) parser.add_argument("--class_weight", type=dict, default=None) parser.add_argument("--verbose", type=bool, default=False) parser.add_argument("--max_iter", type=int, default=-1) parser.add_argument("--decision_function_shape", type=str, default="ovr") parser.add_argument("--break_ties", type=bool, default=False) parser.add_argument("--random_state", type=int, default=42) # parse args args = parser.parse_args() # return args return args # run script if __name__ == "__main__": # parse args args = parse_args() # run main function main(args) ポイントは mlflow.autolog です。 SDK v1の頃はAML独自の書き方でログを記録するのが主流でしたが、 SDK v2からはmlflowで記録を取ることが可能です。これによりAMLのコンピュート クラスタ ー以外でも動作する汎用的な 機械学習 スクリプト が利用可能です。 そして、 mlflow.autolog が有効に使えるコードであれば、この1行でAMLに学習済みモデルの記録や主要なメトリックの登録が行われます。ただし、後述しますがモデルをAMLに登録するには別途操作が必要です。 では、この スクリプト をコンピュート クラスタ ーで実行させましょう。 SDK v2から学習を実行するには先ほどのMLClientを利用し、以下のように書きます。 from azure.ai.ml import command, Input job = command( code="./src", # ローカルの学習スクリプトがあるディレクトリパス command="python train.py --iris-csv ${{inputs.iris}} --C ${{inputs.C}} --kernel ${{inputs.kernel}} --coef0 ${{inputs.coef0}}", inputs={ "iris": Input( type="uri_file", path=my_data.path, ), "C": 0.8, "kernel": "rbf", "coef0": 0.1, }, environment="AzureML-sklearn-1.0-ubuntu20.04-py38-cpu@latest", compute="cpu-cluster", # コンピュートクラスター名 display_name="sklearn-iris-example", experiment_name="iris-experiment", description="AML実験" ) returned_job = ml_client.jobs.create_or_update(job) returned_job ポイントは3つです。 1つ目は、学習に使用する入力デー タセット をInputオブジェクトとして定義することです。定義方法は簡単で、先ほど登録したDataオブジェクトのpathとtypeを引数に与えます。 2つ目は、学習環境となるenvironmentです。今回は学習 スクリプト に複雑な依存関係がなかったのでAMLで用意してくれている環境を指定し、使用しました。こちらは自作の環境も使用できます。 3つ目はcompute引数に先ほど作成したコンピュート クラスタ ー名を指定しているところです。これにより、コンピュート クラスタ ーで学習が実行されます。 このコードを実行するとiris-experimentという実験の配下にsklearn-iris-exampleという名前のジョブで実行情報が以下のように記録されます。 画像にあるように、学習済みモデル、タグ、メトリックなど学習 スクリプト やジョブ実行時に指定していない内容まで自動で記録してくれます。 学習済みモデルの登録 モデルをバージョン管理するために学習済みモデルを登録します。 先ほど実行した学習ジョブには学習済みモデルの情報が記録されています。記録したモデルはAML ワークスペース からも該当のジョブを参照すると確認できます。 SDK から登録するにはジョブ名をコピーして以下のpath引数に与えてModelオブジェクトを作成し、ml_clientからモデルの登録を行います。 from azure.ai.ml.entities import Model from azure.ai.ml.constants import AssetTypes model = Model( path="azureml://jobs/<job name>/outputs/artifacts/paths/model/", name="run-iris-example", description="Model created from run.", type=AssetTypes.CUSTOM_MODEL ) ml_client.models.create_or_update(model) Modelオブジェクトを作成する際のパスのフォーマットはいくつか存在します。このフォーマットは保存したモデルのtypeによって変わります。例えばtypeがmlflowであれば以下のような書き方になります。 model = Model( path="runs:/<job name>/model", name="run-iris-example", description="Model created from run.", type=AssetTypes.CUSTOM_MODEL ) 参考: https://learn.microsoft.com/en-us/azure/machine-learning/how-to-manage-models?tabs=use-job-output%2Ccli デプロイ SDK v2で導入されたマネージドオンライン推論を試してみます。今回はバッチ推論がmlflowモデルをまだサポートしていないので実施しませんが、バッチ推論用のエンドポイントを作成することも可能です。 SDK v2で導入されたマネージドオンライン推論は、その名の通りインフラ管理が不要なマネージドな環境でオンライン推論が実現できます。エンドポイントの細かい管理・運用は必要なく、スケーリングやブルーグリーンデプロイを用いたロールアウトなどの実際の運用時に欲しい設定が簡単にできます。(エンドポイントを作成後削除しない限り料金が発生し続けるので注意です) では早速マネージドオンライン推論の準備を行います。 まずはエンドポイントを作成します。 from azure.ai.ml.entities import ManagedOnlineEndpoint endpoint_name = "iris-test-online-endpoint" endpoint = ManagedOnlineEndpoint( name=endpoint_name, description="this is a sample online endpoint", auth_mode="key", ) ml_client.begin_create_or_update(endpoint).result() 次にデプロイの設定を作成します。推論時の依存関係などの環境や使用するモデル、推論 スクリプト はこのデプロイで設定します。今回デプロイするMLflowモデルの場合、推論用 スクリプト は自動生成されるためデプロイ時に指定する必要はありません。 from azure.ai.ml.entities import ManagedOnlineDeployment blue_deployment = ManagedOnlineDeployment( name="blue", endpoint_name=endpoint_name, # 作成したエンドポイント名 model=model, # モデルオブジェクト instance_type="Standard_DS2_v2", instance_count=1, ) ml_client.online_deployments.begin_create_or_update(blue_deployment).result() 参考: https://learn.microsoft.com/ja-jp/azure/machine-learning/how-to-mlflow-batch?tabs=sdk#using-mlflow-models-with-a-scoring-script 作成したデプロイに全ての トラフィック を割り当てるようエンドポイントを更新します。 endpoint.traffic = {"blue": 100} ml_client.begin_create_or_update(endpoint).result() これでリアルタイム推論用のエンドポイントの完成です。 推論は SDK から以下のようにできます。 ml_client.online_endpoints.invoke( endpoint_name=endpoint_name, deployment_name="blue", request_file="./data/request.json", ) この時、登録したモデルによってリク エス トの json スキーマ が異なるので注意です。今回はMLflowモデルを使用しているので、request. json の中身は以下のようになります。 input_data というキーに対して columns と data を入力します。 index はなくても大丈夫です。 { "input_data": { "columns": [ "sepal length (cm)", "sepal width (cm)", "petal length (cm)", "petal width (cm)" ], "index": [0, 1], "data": [ [2, 3, 4, 5], [6, 5, 4, 3] ] } } 個人的にエンドポイントのテストは SDK から実行するよりも、AML ワークスペース から実行できるのでそちらの方が楽です。 SDK v2の感想 SDK v1の頃に比べると、操作が直感的になり使いやすくなったと感じています。一方で、 SDK v1を使用していた身からすると、使い方が大きく変わりすぎて戸惑うことも多かったです。あとはGAになったばかりということで、ドキュメント通りに動かないものも一部あり、苦労しました。 おわりに 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募をお待ちしています。 ISID AIトランスフォーメーションセンター 採用情報ページ 執筆: @goto.yuki 、レビュー: @yamada.y ( Shodo で執筆されました )
アバター
本記事は Microsoft Azure Tech Advent Calendar 2022 の22日目の記事です X イノベーション 本部 AITC の後藤です。最近、Azureが提供する 機械学習 サービスであるAzure Machine Learning(Azure ML)の SDK v2が新たにGAになりました。 Azure MLは先日 TechPlayのイベント でもご紹介した、私たちのチームが背極的に活用している 機械学習 のさまざまな用途に活用可能なサービスです。 Azure ML SDK v2はv1と大きく使い方が変わりました。本記事ではまだまだ情報が少ないAzure ML SDK v2に関して公式ドキュメントをベースに使い方を紹介します。 そもそもAzure Machine Learning (Azure ML) とは Azure ML SDK v1とv2の比較 Azure ML SDK v2の基本的な使い方 準備 データセット登録 学習の実行 学習済みモデルの登録 デプロイ SDK v2の感想 おわりに そもそもAzure Machine Learning (Azure ML) とは Azure ML は、Azure の 機械学習 サービスです。一言で 機械学習 サービスといってもAzure ML は非常に多くの機能を提供しており、 機械学習 に関連する多くの ユースケース で活用可能です。 機械学習 を実際の業務で運用していくいわゆるMLOpsを実現するには多くの工程が必要です。モデルを学習させるまでも大変ですが、学習後に実際の業務で使用できるように運用していくのはさらに大変です。Azure MLではそういった 機械学習 をビジネスで活用しようとする際に遭遇する多くの問題を解決してくれます。 参考: https://learn.microsoft.com/ja-jp/azure/machine-learning/overview-what-is-azure-machine-learning Azure ML SDK v1とv2の比較 Azure ML SDK v2はv1から大きく変更が加えられ、使い方もほとんど原型がないレベルで変わっています。また、互換性に関してもv1とv2で維持されないものもあります。こういった理由から、v1とv2を同じコードベースで使用することは推奨されていません。 以下は SDK で使用されるクラスを念頭にv1とv2の概念の比較をした表です。 AzureML v1 AzureML v2 Workspace Workspace Datastore Datastore Compute Compute Webservice OnlineEndpoint、OnlineDeployment ParallelRunStep for Batch Scoring BatchEndpoint、BatchDeployment Experiment, Run, Pipeline Job Dataset FileDataset TabularDataset Data assets(v1との整合性が取れていない) URI _FILE URI _FOLDER MLTABLE Model Model Environment Environment 学習ジョブや推論まわりは名称含め大きく変わっていることがわかります。また、上記の表で名前が一緒でも、与える引数が異なるものなども存在します。 参考: https://learn.microsoft.com/ja-jp/azure/machine-learning/how-to-migrate-from-v1#using-v1-and-v2-code-together Azure ML SDK v2の基本的な使い方 それでは基本的な 機械学習 のフローとして以下の内容をAzure ML SDK v2で順番に実行し、使い方を紹介します。 環境準備 デー タセット 登録 学習の実行 学習済みモデルの登録 モデルのデプロイ 準備 Python パッケージの azure-ai-ml をインストールします(ちなみに SDK v1は azureml-core です)。次にAzure portal もしくは Azure CLI でAzure Machine Learning workspace(AML ワークスペース )を作成します。 ワークスペース を作成後、必要な情報をメモし、以下のように Python でMLClientを定義します。 from azure.ai.ml import MLClient from azure.identity import DefaultAzureCredential # AMLワークスペースの情報 subscription_id = "<subscription_id>" resource_group = "<リソースグループ名>" workspace = "<ワークスペース名>" ml_client = MLClient( DefaultAzureCredential(), subscription_id, resource_group, workspace ) SDK v2の主要な変更の一つとして、上記のMLClientが挙げられます。 SDK v1では、ExperimentやRunなど様々なクラスを用途に応じて使い分ける必要がありましたが、v2におけるAMLの操作は全てこのMLClientから実行します。 デー タセット 登録 Azure MLに学習に使用するデータを登録します。 ちなみに細かいですが、v1では データセット と呼称されていたものがv2では データ資産 という呼称に変更されています。本記事では一般的な 機械学習 用語として「デー タセット 」という言葉を使用しています。 参考: https://learn.microsoft.com/ja-jp/azure/machine-learning/how-to-migrate-from-v1#data-datasets-in-v1 Azure MLに登録する際のデータソースには以下の選択肢があります。 ローカルファイル Azure BLOB Storage Azure Blob Storage はAzure のオブジェクトストレージサービスです 参考: https://learn.microsoft.com/ja-jp/azure/storage/blobs/storage-blobs-overview Azure Data Lake Storage Gen2(ADLS gen2) ADLS gen2はAzure Blob Storage をベースに構築された、ビッグ データ分析用途のストレージサービスです Datastore Azure ML で指定するAzureのストレージサービスです 上記のBlobやADSK gen2を指定することで安全にAzure MLからデータにアクセスが可能になります 今回は単純にローカルファイルをAzure MLのデータ資産に登録します。デー タセット 登録は先ほどのMLClientを使用して以下のように書きます。 from azure.ai.ml.entities import Data from azure.ai.ml.constants import AssetTypes # データの場所によりpathの記述方法が異なる # local: './<path>' # blob: 'https://<account_name>.blob.core.windows.net/<container_name>/<path>' # ADLS gen2: 'abfss://<file_system>@<account_name>.dfs.core.windows.net/<path>/' # Datastore: 'azureml://datastores/<data_store_name>/paths/<path>' my_data = Data( path="./data/iris.csv", # ローカルファイルパス type=AssetTypes.URI_FILE, description="irisデータ", name="iris", version='1' # データセットのバージョン ) # AMLにデータセットを登録 ml_client.data.create_or_update(my_data) 参考: https://learn.microsoft.com/ja-jp/azure/machine-learning/how-to-create-data-assets?tabs=CLI#tabpanel_2_Python-SDK 正常に処理が終わるとブラウザでAML ワークスペース のアセット⇨データより以下のように確認できます。 学習の実行 デー タセット の登録が終わったので、次に学習を実行します。 Azure ML SDK v2における学習の実行場所はローカルかコンピュート クラスタ ーの2つです。コンピュート クラスタ ーはAMLにおける学習用の計算リソースです。コンピュート クラスタ ーは事前に作成しておく必要があります。作成は SDK や CLI 、AML ワークスペース からも可能です。 今回はAML ワークスペース からコンピュート クラスタ ーを作成します。AML ワークスペース より左のブレードメニューからコンピューティングを選択し、新規ボタンから作成できます。作成すると以下のようにコンピューティング クラスタ ータブに作成した クラスタ ーが表示されます。 コンピュート クラスタ ーはその名の通り設定した台数で VM が必要に応じてスケールしてくれます。最小の VM 台数を0に設定できるので、学習を実行する時だけ計算リソースを立ち上げることができます。逆に最大の台数を増やせばその分同時に実行できる学習も増やせます。 参考: https://learn.microsoft.com/ja-jp/azure/machine-learning/quickstart-create-resources 続いて、コンピュート クラスタ ー内で実行する学習 スクリプト を用意します。今回はMSのexampleのコードを拝借しました。(引用元: https://github.com/Azure/azureml-examples ) # imports import os import mlflow import argparse import pandas as pd import matplotlib.pyplot as plt from sklearn.svm import SVC from sklearn.model_selection import train_test_split # define functions def main(args): # enable auto logging mlflow.autolog() # setup parameters params = { "C": args.C, "kernel": args.kernel, "degree": args.degree, "gamma": args.gamma, "coef0": args.coef0, "shrinking": args.shrinking, "probability": args.probability, "tol": args.tol, "cache_size": args.cache_size, "class_weight": args.class_weight, "verbose": args.verbose, "max_iter": args.max_iter, "decision_function_shape": args.decision_function_shape, "break_ties": args.break_ties, "random_state": args.random_state, } # read in data df = pd.read_csv(args.iris_csv) # process data X_train, X_test, y_train, y_test = process_data(df, args.random_state) # train model model = train_model(params, X_train, X_test, y_train, y_test) def process_data(df, random_state): # split dataframe into X and y X = df.drop(["species"], axis=1) y = df["species"] # train/test split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=random_state ) # return splits and encoder return X_train, X_test, y_train, y_test def train_model(params, X_train, X_test, y_train, y_test): # train model model = SVC(**params) model = model.fit(X_train, y_train) # return model return model def parse_args(): # setup arg parser parser = argparse.ArgumentParser() # add arguments parser.add_argument("--iris-csv", type=str) parser.add_argument("--C", type=float, default=1.0) parser.add_argument("--kernel", type=str, default="rbf") parser.add_argument("--degree", type=int, default=3) parser.add_argument("--gamma", type=str, default="scale") parser.add_argument("--coef0", type=float, default=0) parser.add_argument("--shrinking", type=bool, default=False) parser.add_argument("--probability", type=bool, default=False) parser.add_argument("--tol", type=float, default=1e-3) parser.add_argument("--cache_size", type=float, default=1024) parser.add_argument("--class_weight", type=dict, default=None) parser.add_argument("--verbose", type=bool, default=False) parser.add_argument("--max_iter", type=int, default=-1) parser.add_argument("--decision_function_shape", type=str, default="ovr") parser.add_argument("--break_ties", type=bool, default=False) parser.add_argument("--random_state", type=int, default=42) # parse args args = parser.parse_args() # return args return args # run script if __name__ == "__main__": # parse args args = parse_args() # run main function main(args) ポイントは mlflow.autolog です。 SDK v1の頃はAML独自の書き方でログを記録するのが主流でしたが、 SDK v2からはmlflowで記録を取ることが可能です。これによりAMLのコンピュート クラスタ ー以外でも動作する汎用的な 機械学習 スクリプト が利用可能です。 そして、 mlflow.autolog が有効に使えるコードであれば、この1行でAMLに学習済みモデルの記録や主要なメトリックの登録が行われます。ただし、後述しますがモデルをAMLに登録するには別途操作が必要です。 では、この スクリプト をコンピュート クラスタ ーで実行させましょう。 SDK v2から学習を実行するには先ほどのMLClientを利用し、以下のように書きます。 from azure.ai.ml import command, Input job = command( code="./src", # ローカルの学習スクリプトがあるディレクトリパス command="python train.py --iris-csv ${{inputs.iris}} --C ${{inputs.C}} --kernel ${{inputs.kernel}} --coef0 ${{inputs.coef0}}", inputs={ "iris": Input( type="uri_file", path=my_data.path, ), "C": 0.8, "kernel": "rbf", "coef0": 0.1, }, environment="AzureML-sklearn-1.0-ubuntu20.04-py38-cpu@latest", compute="cpu-cluster", # コンピュートクラスター名 display_name="sklearn-iris-example", experiment_name="iris-experiment", description="AML実験" ) returned_job = ml_client.jobs.create_or_update(job) returned_job ポイントは3つです。 1つ目は、学習に使用する入力デー タセット をInputオブジェクトとして定義することです。定義方法は簡単で、先ほど登録したDataオブジェクトのpathとtypeを引数に与えます。 2つ目は、学習環境となるenvironmentです。今回は学習 スクリプト に複雑な依存関係がなかったのでAMLで用意してくれている環境を指定し、使用しました。こちらは自作の環境も使用できます。 3つ目はcompute引数に先ほど作成したコンピュート クラスタ ー名を指定しているところです。これにより、コンピュート クラスタ ーで学習が実行されます。 このコードを実行するとiris-experimentという実験の配下にsklearn-iris-exampleという名前のジョブで実行情報が以下のように記録されます。 画像にあるように、学習済みモデル、タグ、メトリックなど学習 スクリプト やジョブ実行時に指定していない内容まで自動で記録してくれます。 学習済みモデルの登録 モデルをバージョン管理するために学習済みモデルを登録します。 先ほど実行した学習ジョブには学習済みモデルの情報が記録されています。記録したモデルはAML ワークスペース からも該当のジョブを参照すると確認できます。 SDK から登録するにはジョブ名をコピーして以下のpath引数に与えてModelオブジェクトを作成し、ml_clientからモデルの登録を行います。 from azure.ai.ml.entities import Model from azure.ai.ml.constants import AssetTypes model = Model( path="azureml://jobs/<job name>/outputs/artifacts/paths/model/", name="run-iris-example", description="Model created from run.", type=AssetTypes.CUSTOM_MODEL ) ml_client.models.create_or_update(model) Modelオブジェクトを作成する際のパスのフォーマットはいくつか存在します。このフォーマットは保存したモデルのtypeによって変わります。例えばtypeがmlflowであれば以下のような書き方になります。 model = Model( path="runs:/<job name>/model", name="run-iris-example", description="Model created from run.", type=AssetTypes.CUSTOM_MODEL ) 参考: https://learn.microsoft.com/en-us/azure/machine-learning/how-to-manage-models?tabs=use-job-output%2Ccli デプロイ SDK v2で導入されたマネージドオンライン推論を試してみます。今回はバッチ推論がmlflowモデルをまだサポートしていないので実施しませんが、バッチ推論用のエンドポイントを作成することも可能です。 SDK v2で導入されたマネージドオンライン推論は、その名の通りインフラ管理が不要なマネージドな環境でオンライン推論が実現できます。エンドポイントの細かい管理・運用は必要なく、スケーリングやブルーグリーンデプロイを用いたロールアウトなどの実際の運用時に欲しい設定が簡単にできます。(エンドポイントを作成後削除しない限り料金が発生し続けるので注意です) では早速マネージドオンライン推論の準備を行います。 まずはエンドポイントを作成します。 from azure.ai.ml.entities import ManagedOnlineEndpoint endpoint_name = "iris-test-online-endpoint" endpoint = ManagedOnlineEndpoint( name=endpoint_name, description="this is a sample online endpoint", auth_mode="key", ) ml_client.begin_create_or_update(endpoint).result() 次にデプロイの設定を作成します。推論時の依存関係などの環境や使用するモデル、推論 スクリプト はこのデプロイで設定します。今回デプロイするMLflowモデルの場合、推論用 スクリプト は自動生成されるためデプロイ時に指定する必要はありません。 from azure.ai.ml.entities import ManagedOnlineDeployment blue_deployment = ManagedOnlineDeployment( name="blue", endpoint_name=endpoint_name, # 作成したエンドポイント名 model=model, # モデルオブジェクト instance_type="Standard_DS2_v2", instance_count=1, ) ml_client.online_deployments.begin_create_or_update(blue_deployment).result() 参考: https://learn.microsoft.com/ja-jp/azure/machine-learning/how-to-mlflow-batch?tabs=sdk#using-mlflow-models-with-a-scoring-script 作成したデプロイに全ての トラフィック を割り当てるようエンドポイントを更新します。 endpoint.traffic = {"blue": 100} ml_client.begin_create_or_update(endpoint).result() これでリアルタイム推論用のエンドポイントの完成です。 推論は SDK から以下のようにできます。 ml_client.online_endpoints.invoke( endpoint_name=endpoint_name, deployment_name="blue", request_file="./data/request.json", ) この時、登録したモデルによってリク エス トの json スキーマ が異なるので注意です。今回はMLflowモデルを使用しているので、request. json の中身は以下のようになります。 input_data というキーに対して columns と data を入力します。 index はなくても大丈夫です。 { "input_data": { "columns": [ "sepal length (cm)", "sepal width (cm)", "petal length (cm)", "petal width (cm)" ], "index": [0, 1], "data": [ [2, 3, 4, 5], [6, 5, 4, 3] ] } } 個人的にエンドポイントのテストは SDK から実行するよりも、AML ワークスペース から実行できるのでそちらの方が楽です。 SDK v2の感想 SDK v1の頃に比べると、操作が直感的になり使いやすくなったと感じています。一方で、 SDK v1を使用していた身からすると、使い方が大きく変わりすぎて戸惑うことも多かったです。あとはGAになったばかりということで、ドキュメント通りに動かないものも一部あり、苦労しました。 おわりに 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募をお待ちしています。 ISID AIトランスフォーメーションセンター 採用情報ページ 執筆: @goto.yuki 、レビュー: @yamada.y ( Shodo で執筆されました )
アバター
みなさんこんにちは! 金融ソリューション事業部の市場系ソリューション1部の寺山です。12 月に入ってから一気に寒くなったように感じます。 本記事は 電通国際情報サービス Advent Calendar 2022 の 12 月 22 日の記事です。 昨年の アドベントカレンダー でテックブログに参加して以来、普段の業務においても「何か記事にできることはないかな?」と考えるようになり、一周り充実した1年でした! 今回は、 AWS Fargate で実行した Web アプリケーション(バックエンド API )のマルチスレッドが想定外の挙動となった点をインフラ視点で調査したので、発生した事象とその対応方法について紹介いたします! 発生した事象 概要 調査開始時の予想 原因 JavaのavailableProcessors cgroupのcpu.sharesパラメータ 対応 コンテナ定義のcpuパラメータの指定 XX:ActiveProcessorCountオプションの利用 非同期処理を検討する おわりに 発生した事象 概要 バックエンド API をホストする クラウド 構成は下図のとおりです。本件で触れないサービスは割愛しています。 バックエンド API はコンテナ化し、 AWS Fargate で ECS タスクとして起動します。 API リク エス トは Application Load Balancer が受け付け、ターゲットグループに登録した ECS サービスへルーティングする典型的な クラウド アーキテクチャ です。 バックエンド API は Java (11)で実装されています。 事象は、レスポンスの返却に時間を要するリク エス トの処理中に発生しました。 ALB のターゲットグループのヘルスチェックは、バックエンド API のヘルスチェック用エンドポイントを指定していました。このエンドポイントがヘルスチェックの タイムアウト 時間内に正常レスポンスを返却しないことを 閾値 の回数以上に繰り返したため、クライアントのリク エス ト処理中の Fargate タスクが Unhealthy と判定され、停止されてしまいました。 タスクの停止理由に、 Task failed ELB health checks in (target-group arn:aws:elasticloadbalancing:ap-northeast-1:XXXXXXXXXXXX:targetgroup/m5infr-sandb-D87C6CX3M8FL/a5285befd3edc05d) と出力されています。 調査開始時の予想 私は事象の初回発生時、当該システム/プロジェクトには関与しておらず、後に調査を引き取った形になります。 この事象の話を聞いた当初は CPU リソースの不足が理由だろうからタスクサイズをチューニングすればよい(バックエンド API のタスクサイズは vCPU:1 でした)ものだと考えていました。 実際に検証したところ、私の予想とは異なる結果となりました。まずはその調査結果を紹介します。 事象が発生したアプリケーションとは別にアプリチームが調査用のアプリケーションを用意してくれたので、それを同じ構成で AWS にデプロイし検証しました。 4vCPU を割り当てた Fargate タスクで再現するか検証します。 API リク エス トは、コンテナイメージの手動ビルド用途として作成した EC2 インスタンス で行いました。 $ curl http://<ALBのDNS名>:8080/sandbox/greet ; echo everyone! hello!everyone! /sandbox/greet はリク エス トに対し、即座に hello! と返却する API です。上記のように正常時は curl コマンドでレスポンスを取得できます。 $ (curl http://<ALBのDNS名>:8080/sandbox/sleep ; echo done at `date`) & [1] 3087 $ curl http://<ALBのDNS名>:8080/sandbox/greet ; echo everyone! <html> <head><title>502 Bad Gateway</title></head> <body> <center><h1>502 Bad Gateway</h1></center> </body> </html> <html> <head><title>502 Bad Gateway</title></head> <body> <center><h1>502 Bad Gateway</h1></center> </body> </html> everyone! 次は、 /sandbox/sleep エンドポイントをバックグラウンドジョブとして実行し、ジョブが終了した日付を表示するようにしています。 /sandbox/sleep は TimeUnit.sleep を無期限に実行する API です。応答に時間を要する API というのを簡易的に表現しています。 バックグラウンドジョブの後に /sandbox/greet を curl していますが、今度はレスポンスを取得できずに API コンテナが終了したため 502 エラーとなっています。 実際にタスクも停止されており、事象が再現しています。 sleep でアイドル状態になっているので自明ではあるのですが、リソース使用率も低いことがメトリクスより確認できます。 つまり、タスクサイズのチューニングにより解消するという私の予想には反して、本件はタスクサイズに依らず発生する事象であることがわかりました。 原因 Java のavailableProcessors バックエンド API は Java のマイクロサービス軽量 フレームワーク である Helidon を使用しています。 この フレームワーク ではデフォルトの動作として、スレッドプール数を Runtime.getRuntime().availableProcessors() の戻り値に設定します。 1 ECS/Fargate では availableProcessors が vCPU のサイズに依らず 1 となるため、すでにスレッドを使用中の場合に、リク エス トを処理するためのスレッドを新たに取得できないことが原因です。 実際に確認してみます。 確認は、 ECS Exec を利用して Fargate 内で CLI 操作をすることにより行いました。 対象のタスクは先ほどと同じ 4vCPU を割り当てています。 $ aws ecs describe-tasks --cluster m5infrasurvey-poc-ecs-cluster --tasks 6218b4dd29c146a6ac120c5395a6e462 { "tasks": [ { # 省略 "cpu": "4096", # 省略 "memory": "8192", # 省略 } ], "failures": [] } ECS Exec よりコンテナのシェルにアクセスし、 JShell より availableProcessors を実行します。 $ aws ecs execute-command --cluster m5infrasurvey-poc-ecs-cluster --task 6218b4dd29c146a6ac120c5395a6e462 --container sandbox --interactive --command "sh" The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Starting session with SessionId: ecs-execute-command-0dc2b2f03561dd7b9 # # jshell Dec 19, 2022 5:33:04 PM java.util.prefs.FileSystemPreferences$1 run INFO: Created user preferences directory. | Welcome to JShell -- Version 11.0.16.1 | For an introduction type: /help intro jshell> Runtime.getRuntime().availableProcessors() $1 ==> 1 jshell> /exit | Goodbye 戻り値が 1 であることを確認しました。 比較として、EC2 インスタンス として起動している Amazon Linux2 に Docker Server をインストールし、同じ Docker イメージを起動して確認してみます。 docker exec コマンドでシェルにアクセスして、上記同様に JShell を実行します。 $ docker exec -it sandbox "sh" # # lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 46 bits physical, 48 bits virtual CPU(s): 8 On-line CPU(s) list: 0-7 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 85 Model name: Intel(R) Xeon(R) Platinum 8124M CPU @ 3.00GHz Stepping: 4 CPU MHz: 3416.106 BogoMIPS: 5999.99 Hypervisor vendor: KVM Virtualization type: full L1d cache: 128 KiB L1i cache: 128 KiB L2 cache: 4 MiB L3 cache: 24.8 MiB NUMA node0 CPU(s): 0-7 Vulnerability Itlb multihit: KVM: Mitigation: VMX unsupported Vulnerability L1tf: Mitigation; PTE Inversion Vulnerability Mds: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Vulnerability Meltdown: Mitigation; PTI Vulnerability Mmio stale data: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Vulnerability Retbleed: Vulnerable Vulnerability Spec store bypass: Vulnerable Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Retpolines, STIBP disabled, RSB filling Vulnerability Srbds: Not affected Vulnerability Tsx async abort: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe 1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowpr efetch invpcid_single pti fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves ida arat pku ospke # # jshell Dec 20, 2022 9:07:44 AM java.util.prefs.FileSystemPreferences$1 run INFO: Created user preferences directory. | Welcome to JShell -- Version 11.0.16.1 | For an introduction type: /help intro jshell> Runtime.getRuntime().availableProcessors() $1 ==> 8 jshell> /exit | Goodbye インスタンス タイプは c5.2xlarge です。参考として上記では lscpu も実行しています。 コンテナ起動時に CPU オプションは指定していないため、ホストの CPU がそのまま見えています。 availableProcessors() は EC2 インスタンス の vCPU 数を返却しており、ECS/Fargate とは挙動が異なっています。 cgroupのcpu.sharesパラメータ コンテナ仮想化におけるリソース割り当てに利用されるcgroupのcpuサブシステムのsharesパラメータは、コンテナが 利用可能なCPUリソースのスケジューリングに利用されます。Dockerでは run コマンドの --cpu-shares パラメータで指定します。 割合は cpu.shares パラメータの値を 1024 で除算した値で計算されます。 Dockerの場合はこのパラメータにデフォルトで 1024 が適用されます。 https://docs.docker.jp/config/container/resource_constraints.html#cfs コンテナへの配分を定めるもので、デフォルト値は1024 です。 つまり、ホスト上で実行するコンテナが1台で他にリソース制限を付与するパラメータが指定されていない場合、割合は 1024/1024=1 となりホストの CPUリソースを全て利用可能となります。 一方、ECSではデフォルト値として cpu.shares に 2 が適用されます。 https://aws.amazon.com/jp/blogs/news/how-amazon-ecs-manages-cpu-and-memory-resources/ 注: コンテナに CPU ユニットを指定しない場合、ECS は内部的に cgroup に対して 2 ( Linux カーネル が許容する最小値) という値の Linux CPU 配分 (cpu.shares) を設定します。 Fargateタスクに対し、 Java の -Xlog:os+container=trace オプションを使用して JVM のリソース認識状況を出力したものが以下になります。 $ aws ecs execute-command --cluster m5infrasurvey-poc-ecs-cluster --task 83d8e044611d4d22bbeaf65d164af34f --container sandbox --interactive --command "/bin/sh" The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Starting session with SessionId: ecs-execute-command-0dc479f32fcd9891d # # java -Xlog:os+container=trace -version [0.000s][trace][os,container] OSContainer::init: Initializing Container Support [0.001s][debug][os,container] Detected cgroups hybrid or legacy hierarchy, using cgroups v1 controllers [0.001s][trace][os,container] Path to /memory.use_hierarchy is /sys/fs/cgroup/memory/memory.use_hierarchy [0.001s][trace][os,container] Use Hierarchy is: 1 [0.001s][trace][os,container] Path to /memory.limit_in_bytes is /sys/fs/cgroup/memory/memory.limit_in_bytes [0.001s][trace][os,container] Memory Limit is: 9223372036854771712 [0.001s][trace][os,container] Non-Hierarchical Memory Limit is: Unlimited [0.001s][trace][os,container] Path to /memory.stat is /sys/fs/cgroup/memory/memory.stat [0.001s][trace][os,container] Hierarchical Memory Limit is: 8589934592 [0.001s][info ][os,container] Memory Limit is: 8589934592 [0.001s][trace][os,container] Path to /cpu.cfs_quota_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us [0.001s][trace][os,container] CPU Quota is: -1 [0.001s][trace][os,container] Path to /cpu.cfs_period_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us [0.001s][trace][os,container] CPU Period is: 100000 [0.001s][trace][os,container] Path to /cpu.shares is /sys/fs/cgroup/cpu,cpuacct/cpu.shares [0.001s][trace][os,container] CPU Shares is: 2 [0.001s][trace][os,container] CPU Share count based on shares: 1 [0.001s][trace][os,container] OSContainer::active_processor_count: 1 [0.001s][trace][os,container] CgroupSubsystem::active_processor_count (cached): 1 [0.001s][trace][os,container] CgroupSubsystem::active_processor_count (cached): 1 # 省略 実際に CPU Share is: 2 と出力されていることが確認できました。 2/1024≒0.002 のためコンテナに割り当てられる CPUリソース数が極端に少なく、 JVM は利用可能な CPU 数の最小値である 1 を適用する挙動をしたと判明しました。ECS/FargateとDockerにて挙動の異なる点にも説明がつきます。 対応 コンテナ定義のcpuパラメータの指定 前のセクションで説明した通り、 cpu.shares がデフォルトでは 2 であることが、タスクサイズに依らず1スレッドしか利用できない事象の深層的な原因であるとわかりました。 ECSにおいて cpu.shares を指定するには、コンテナ定義の cpu パラメータを指定すれば良いです。タスク定義におけるタスクサイズの cpu とは別のパラメータであることに注意してください。 私の検証では環境構築に AWS CDK を利用したので、対応例として ソースコード の一部を紹介します。 利用しているバージョンは package.json の一部の紹介で代替いたします。 { //省略 " devDependencies ": { " @types/jest ": " ^29.2.3 ", " @types/node ": " ^18.11.9 ", " @typescript-eslint/eslint-plugin ": " ^5.20.0 ", " @typescript-eslint/parser ": " ^5.20.0 ", " aws-cdk ": " ^2.51.0 ", " eslint ": " ^8.13.0 ", " eslint-config-prettier ": " ^8.5.0 ", " jest ": " ^29.3.1 ", " prettier ": " ^2.6.2 ", " ts-jest ": " ^29.0.3 ", " ts-node ": " ^10.9.1 ", " typescript ": " ^4.9.3 " } , " dependencies ": { " aws-cdk-lib ": " ^2.51.0 ", " constructs ": " ^10.0.0 ", " source-map-support ": " ^0.5.16 " } } const sandboxTaskdef = new TaskDefinition ( this , 'sandboxTaskdef' , { compatibility: Compatibility.FARGATE , networkMode: NetworkMode.AWS_VPC , family: ` ${ SYSTEM_NAME } - ${ props.m5Props.m5Env } -ecs-taskdef-sandbox` , executionRole: sandboxTaskRole , taskRole: sandboxTaskRole , cpu: '4096' , memoryMiB: '8192' , } ); sandboxTaskdef.addContainer ( 'sandbox' , { image: ContainerImage.fromEcrRepository ( Repository.fromRepositoryArn ( this , 'sandbox' , 'arn:aws:ecr:ap-northeast-1:XXXXXXXXXXXX:repository/m5infrasurvey-poc-ecr-sandbox' ) ), containerName: 'sandbox' , cpu: 1024 , // cpu.sharesに設定される /*省略*/ } ); 宣言した TaskDefinition クラスの addContaier メソッドからコンテナ定義の cpu パラメータを指定しています。 上記のコードをデプロイして起動されたFargateタスクの cpu.shares を実際に確認してみます。 まずは起動されたタスクの情報です。 $ aws ecs describe-tasks --cluster m5infrasurvey-poc-ecs-cluster --tasks 46e1d6fd883c47419093ad0e84a41ad1 { "tasks": [ { # 省略 "containers": [ { "containerArn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:container/m5infrasurvey-poc-ecs-cluster/46e1d6fd883c4741 9093ad0e84a41ad1/342343c2-0d87-4cdf-b60a-4496307cfc33", "taskArn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task/m5infrasurvey-poc-ecs-cluster/46e1d6fd883c47419093ad0e84 a41ad1", "name": "sandbox", # 省略 "managedAgents": [ { "lastStartedAt": "2022-12-21T17:30:48.947000+09:00", "name": "ExecuteCommandAgent", "lastStatus": "RUNNING" } ], "cpu": "1024" } ], "cpu": "4096", # 省略 } ], "failures": [] } タスクサイズとは別にコンテナ定義として "cpu": "1024" が反映されていることを確認しました。 続いて、 JVM が認識するリソース情報と availableProcessors の戻り値を確認します。 $ aws ecs execute-command --cluster m5infrasurvey-poc-ecs-cluster --task 46e1d6fd883c47419093ad0e84a41ad1 --container sandbox --interactive --command "/bin/sh" The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Starting session with SessionId: ecs-execute-command-0d914bdaa48aec635 # # java -Xlog:os+container=trace -version [0.000s][trace][os,container] OSContainer::init: Initializing Container Support [0.001s][debug][os,container] Detected cgroups hybrid or legacy hierarchy, using cgroups v1 controllers [0.001s][trace][os,container] Path to /memory.use_hierarchy is /sys/fs/cgroup/memory/memory.use_hierarchy [0.001s][trace][os,container] Use Hierarchy is: 1 [0.001s][trace][os,container] Path to /memory.limit_in_bytes is /sys/fs/cgroup/memory/memory.limit_in_bytes [0.001s][trace][os,container] Memory Limit is: 9223372036854771712 [0.001s][trace][os,container] Non-Hierarchical Memory Limit is: Unlimited [0.001s][trace][os,container] Path to /memory.stat is /sys/fs/cgroup/memory/memory.stat [0.001s][trace][os,container] Hierarchical Memory Limit is: 8589934592 [0.001s][info ][os,container] Memory Limit is: 8589934592 [0.001s][trace][os,container] Path to /cpu.cfs_quota_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us [0.001s][trace][os,container] CPU Quota is: -1 [0.001s][trace][os,container] Path to /cpu.cfs_period_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us [0.001s][trace][os,container] CPU Period is: 100000 [0.001s][trace][os,container] Path to /cpu.shares is /sys/fs/cgroup/cpu,cpuacct/cpu.shares [0.001s][trace][os,container] CPU Shares is: 1024 [0.001s][trace][os,container] OSContainer::active_processor_count: 4 [0.001s][trace][os,container] CgroupSubsystem::active_processor_count (cached): 4 [0.001s][trace][os,container] CgroupSubsystem::active_processor_count (cached): 4 [0.037s][trace][os,container] Path to /cpu.cfs_quota_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us [0.038s][trace][os,container] CPU Quota is: -1 [0.038s][trace][os,container] Path to /cpu.cfs_period_us is /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us [0.038s][trace][os,container] CPU Period is: 100000 [0.038s][trace][os,container] Path to /cpu.shares is /sys/fs/cgroup/cpu,cpuacct/cpu.shares [0.038s][trace][os,container] CPU Shares is: 1024 [0.038s][trace][os,container] OSContainer::active_processor_count: 4 # 省略 # # jshell Dec 21, 2022 5:55:00 PM java.util.prefs.FileSystemPreferences$1 run INFO: Created user preferences directory. | Welcome to JShell -- Version 11.0.16.1 | For an introduction type: /help intro jshell> Runtime.getRuntime().availableProcessors() $1 ==> 4 以下が確認できました。 CPU Shares is: 1024 と出力され、 cpu.shares が意図通りに設定されていること active_processor_count: 4 と出力され、タスクのvCPUと同値が認識されていること availableProcessors の戻り値が 4 と出力され、タスクのvCPUと同値であること 最後に、本件の事象が解消しているかを事象の再現方法と同じように確認します。 $ curl http://m5infrasurvey-poc-alb-sandbox-352400359.ap-northeast-1.elb.amazonaws.com:8080/sandbox/greet ; echo everyone! hello!everyone! $ $ (curl http://m5infrasurvey-poc-alb-sandbox-352400359.ap-northeast-1.elb.amazonaws.com:8080/sandbox/sleep ; echo done at `date`) & [1] 4454 $ (curl http://m5infrasurvey-poc-alb-sandbox-352400359.ap-northeast-1.elb.amazonaws.com:8080/sandbox/sleep ; echo done at `date`) & [2] 4457 $ (curl http://m5infrasurvey-poc-alb-sandbox-352400359.ap-northeast-1.elb.amazonaws.com:8080/sandbox/sleep ; echo done at `date`) & [3] 4460 $ $ curl http://m5infrasurvey-poc-alb-sandbox-352400359.ap-northeast-1.elb.amazonaws.com:8080/sandbox/greet ; echo everyone! hello!everyone! スレッドプール数は 4 になっているので、事象の再現に使用した /sandbox/sleep API を 3 リク エス ト受け付け後も、 /sandbox/greet API からレスポンスを取得できています。想定通りの結果を得ることができました。 XX:ActiveProcessorCountオプションの利用 前のセクションの対応で、 cpu.shares の設定方法がわかりました。 しかしながら、 18.0.2+ , 17.0.5+ , 11.0.17+ からOpenJDKにて cpu.shares を用いて利用可能なCPU数の制御を廃止する( 2 )ため、 Java のバージョン次第で前述した方法では事象が再現します。 そこで、 Java の拡張ランタイムオプションである -XX:ActiveProcessorCount=N を利用します。このパラメータを利用すると、 JVM が認識する CPU の数を上書きできます。 前述した対応ではコンテナのリソース量を環境に合わせて動的に割り当て可能ですが、こちらの場合は環境ごとにパラメータを変更する必要があります。 -XX:ActiveProcessorCount=N を利用するケースにおいても、対応例として AWS CDKの ソースコード の一部を紹介します。 const cpuNum = 4 ; const sandboxTaskdef = new TaskDefinition ( this , 'sandboxTaskdef' , { compatibility: Compatibility.FARGATE , networkMode: NetworkMode.AWS_VPC , family: ` ${ SYSTEM_NAME } - ${ props.m5Props.m5Env } -ecs-taskdef-sandbox` , executionRole: sandboxTaskRole , taskRole: sandboxTaskRole , cpu: String ( cpuNum * 1024 ), memoryMiB: '8192' , } ); sandboxTaskdef.addContainer ( 'sandbox' , { image: ContainerImage.fromEcrRepository ( Repository.fromRepositoryArn ( this , 'sandbox' , 'arn:aws:ecr:ap-northeast-1:XXXXXXXXXXXX:repository/m5infrasurvey-poc-ecr-sandbox' ) ), containerName: 'sandbox' , environment: { TZ: 'Asia/Tokyo' , JAVA_TOOL_OPTIONS: `-XX:ActiveProcessorCount= ${ cpuNum } ` , // 環境変数で-XX:ActiveProcessorCountを設定 } , } ); タスクに割り当てる vCPU の数をあらかじめ宣言しておき( cpuNum )、タスクサイズの cpu パラメータと -XX:ActiveProcessorCount= の値に使用します。 本番環境と開発環境間でタスクサイズは異なることが多いので、 -XX:ActiveProcessorCount= は JAVA_TOOL_OPTIONS 環境変数 を利用して外部から注入する形で付与するのがベターと考えました。 このように クラウド のサービスやリソースを宣言的に構築できる点や、値やオブジェクト等を再利用可能なところもIaCのメリットですね! 前のセクションと同じように反映されたか確認します。 $ aws ecs execute-command --cluster m5infrasurvey-poc-ecs-cluster --task e4696c0103574ad0a07a8eabc1360087 --container sandbox --interactive --command "/bin/sh" The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Starting session with SessionId: ecs-execute-command-0c5d73c5e08474f67 # # jshell Picked up JAVA_TOOL_OPTIONS: -XX:ActiveProcessorCount=4 Dec 20, 2022 6:58:07 PM java.util.prefs.FileSystemPreferences$1 run INFO: Created user preferences directory. | Welcome to JShell -- Version 11.0.16.1 | For an introduction type: /help intro jshell> Runtime.getRuntime().availableProcessors() $1 ==> 4 JAVA_TOOL_OPTIONS 環境変数 が適用されていること、および、 availableProcessors の戻り値も指定した値になっていることが確認できました。 実際に API にリク エス トを行う動作確認は前述と同じ結果になったため記載は割愛します。 非同期処理を検討する 前述までの対応は、タスクの vCPU を 2 以上に設定することが前提となっています。 常時多数のリク エス トを受けるつけるようなワークロードであればよいですが、それ以外の場合はリソースに余剰が発生しコスト最適化の設計原則に反しています。 理想は、必要最小限のタスクサイズとタスク数で ECS サービスを起動し、負荷に応じてスケールすることです。 今回の発端となったような応答まで時間を要するような処理は非同期で実行することで、スレッドを長時間占有することを回避するという案です。 AWS においては SQS と Lambda を利用してメッセージキューイングによる非同期処理をサーバレス アーキテクチャ で実現する方法が知られています。 上記の図の例では、バックエンド API は enqueue に成功した時点でレスポンスを返却するため、本件の事象を回避するという意図になります。 非同期化した場合は API クライアント側にポーリングやリフレッシュの仕組みが必要になるため、 API 仕様として許容可能かの調整も必要となる点には注意が必要です。 おわりに 今回は ECS/Fargate で発生した事象について紹介いたしました。 今後は他の クラウド プロバイダのコンテナ関連サービスにおける事象の発生有無についても調査したいと考えています。 最後までご覧いただきありがとうございました。皆さま良いお年を! 私たちは一緒に働いてくれる仲間を募集しています! 金融機関のようなお客様においても クラウド ファーストなアプローチやマイクロサービスのようなモダンな技術の採用が進みつつあります。ご自身のインフラ/ クラウド スキルを エンタープライズ 領域でも活用したい・挑戦したいという方がいらっしゃいましたら、ぜひご応募ください! 金融システム インフラ・アーキテクト 金融システム インフラエンジニア 執筆: 寺山 輝 (@terayama.akira) 、レビュー: @mizuno.kazuhiro ( Shodo で執筆されました ) https://github.com/helidon-io/helidon/blob/helidon-2.x/webserver/webserver/src/main/java/io/helidon/webserver/ServerConfiguration.java#L58-L65 , https://github.com/helidon-io/helidon/blob/helidon-3.x/common/configurable/src/main/java/io/helidon/common/configurable/ServerThreadPoolSupplier.java#L54-L68 ↩ https://bugs.openjdk.org/browse/JDK-8281181 ↩
アバター
こんにちは、X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループ 新卒入社2年目の大西です。これは、 電通国際情報サービス Advent Calendar 2022 12/21の記事です。今日は、私が所属するセキュリティグループについて記事を書いてみました!配属されて1年余りですが、グループの仕事内容や雰囲気、魅力などをお伝えできればと思います。 どうしてセキュリティグループに入ったの?? どんな仕事をしているの?? やりがいや達成感は?? 大変なところは?? グループの雰囲気はどんなかんじ?? ブログを見てくださった方に向けて どうしてセキュリティグループに入ったの?? 昨年セキュリティグループに配属された際、いろんな人に「どうしてセキュリティの仕事がしたいの?」と聞かれました。私は、入社する前からセキュリティに興味があり、セキュリティに関わる仕事がしたいと思っていました。その理由は、いくつかあります。 セキュリティの仕事=必要な仕事 世の中にはいろんな便利なものがありますが、便利なだけでは人は満足しないと思います。便利さ以前に、「安心」がないと、誰もそれを使ってくれません。例えば、音楽のストリーミングサービスで、いろんな曲が定額で聴き放題だけど、クレジットカードなどの支払い情報が外部に ダダ漏れ だったら誰もそれを使いたいとは思いません。 どんな便利なアプリやシステムでも絶対に必要なもの...安心とかセキュリティとか...それらを守る仕事がしたいとずっと思っていて、それを新人の時に人事の方に伝え、無事セキュリティグループに配属されました!(ISIDは、できるだけ希望の部署に入れてあげたいという思いの強い会社だと思います。) セキュリティエンジニア、足りてない... ニュースで セキュリティインシデント を目にすることはたまにしかないかもしれませんが、 セキュリティインシデント は毎日起こっています。 Security Next というサイトでは、情報セキュリティに関するニュースを日刊で届けてくれるのですが、これを見ていると本当に毎日インシデントだらけだなぁと思います。しかし、それらインシデントに対応できるようなセキュリティエンジニアは不足していて、多くの企業で課題となっています。セキュリティ人材が足りていない中で、少しでも貢献したいという気持ちがありました。今年もセキュリティグループに新たに4人の方が採用されて、やはりニーズは高まっているんだなと感じます。 セキュリティエンジニア、かっこいい! 私がセキュリティに興味を持ち始めたのは、ハッキングをしている人たちをTVで見て、ハッキングって面白そう!と思ったところからでした。そこから、 IPUSIRON さんの 「ハッキング・ラボの作り方」 を読みながら簡単なハッキングをしてみたり、CTF(Capture The Flag)と呼ばれるセキュリティコンテストの問題を解いてみたりして、ますます面白いと思うようになりました。 ハッキングの知識を悪いことに使えば犯罪ですが、いいことに使えば世の中のいろんなシステムを攻撃から守れます。セキュリティエンジニアは、プログラミングの知識だけでなく、ネットワーク、暗号化・認証技術、 クラウド 技術など様々なことに精通している必要があります。システムやアプリに存在するセキュリティの穴をどう塞ぐか、いろんな知識を持ち寄って考えなければなりません。それが、大変ではあるけれど、とてもかっこいいなと感じます! どんな仕事をしているの?? 現在9名のメンバーがセキュリティグループに所属し、部門横断で システム開発 ・運用におけるセキュリティを確保する支援を行っています。SRB(Security Review Board), SOC(Security Operation Center), IRG(Incident Response Group)という3つのグループがあり、図にすると以下のような感じです。 これまではSRB中心の業務でしたが、今年SOCとIRGチームが立ち上がりました。それぞれのグループの業務内容については以下のとおりです。 SRB: ISIDの事業部が担当している案件においてセキュリティに関する懸念事項を確認し、リリース前までに修正した状態にします。具体的には、案件の提案段階や設計段階でセキュリティに関する懸念事項がないか確認したり、セキュリティツールを用いた検査を行ったりし、事業部側に改善案を提案します。 SOC: クラウド 環境のセキュリティ対策を可視化し、被害の拡大を防止して問題の早期検知を行います。各案件のセキュリティ設定基準に対する準拠状況を把握し、チェックした結果を案件担当に伝え、改善を進めていきます。脅威検知ではアラートに対して、案件担当と連携し問題調査を行います。また、案件担当者に対し、 クラウド 専門の部署と合同でセキュリティ勉強会を行ったりもします。 IRG: セキュリティインシデント が発生した際に、状況を把握して被害の拡大を防止したり、被害の範囲を特定するなど適切に事後対応を行います。また、インシデントが起きた原因を突き止めそれを教訓化することで、将来のインシデントのリスクを低減させます。 この中で私はSRBチームに所属しており、セキュリティツールを用いた 脆弱性 検査(動的検査)の担当しています(実は、担当になったばかりで先月キャッチアップを終え、実際に案件を担当するのはこれからです!)。他にも、社内向けWeb アプリ開発 や、新人研修のセキュリティ講義、また、ISIDで使用しているソフトウェアやハードウェアなどの 脆弱性 が公開されればそれを全社に通知する業務なども担当しています。来年からは、セキュリティツールでは見つけにくい 脆弱性 を発見するため、マニュアル検査が本格的に始まります。実際に擬似的な攻撃コードなどを投げ、返ってきたレスポンスを見て脆弱かどうか検査していくのですが、経験と専門性がより問われるので、これから頑張って技術を習得していきたいと思います! やりがいや達成感は?? 業務によってやりがいや達成感を感じる瞬間は違いますが、例えばWeb アプリ開発 では、一つの機能を作り終えた時にすごくやりがいを感じますし、新人研修のセキュリティ講義をしたときは、講義やハッキング演習を通して新人がセキュリティに興味を持ってくれた時にもすごくやりがいを感じました。 これから 脆弱性 検査を本格的に担当していくと、リリース前に 脆弱性 が見つけられた時などにも、 セキュリティインシデント を未然に防ぐことができて大きなやりがいを感じるのではないかと思います。セキュリティ要件の高い案件や大きな規模の案件のセキュリティレビューや 脆弱性 検査をすることもあり、金融機関や官公庁の案件を担当することもあります。それらの案件のシステムがもし サイバー攻撃 を受けると、影響を受ける範囲がとても大きくなるので、それを未然に防ぐ仕事というのはとてもやりがいがあるのではないでしょうか。 大変なところは?? 身に付けなければならない知識・スキルが多いことです。それは最初から覚悟していたことなのですが、覚えても覚えても新しい技術が出てくるので、心が折れそうになることもあります。しかし、一歩ずつやっていくしかないと思うので、毎日の業務で新しいことを覚えたり、資格の勉強をしたりして地道に知識を増やしていきます。資格の勉強は、体系的にセキュリティのことを学べるのでいいなと思います。ISIDは資格試験や研修の費用を会社がお金を出してくれるのでとてもありがたいです。資格勉強に限らずですが、勉強を進めていると、初め聞いた時はよくわからなかった技術も他に新しいことを覚えていくうちに、あ、あの時のあれはそういうことだったんだ!!みたいな、点と点が繋がる瞬間がたまにあって、自分の成長を感じる瞬間がとても嬉しいです。この仕事は、本当に日々勉強です。 グループの雰囲気はどんなかんじ?? セキュリティグループの雰囲気は、「個」というよりは「チーム」、「上下関係」というよりは「フラット」、「静か」というよりは「にぎやか」という感じです。みんなで助け合いという感じで、今は助けられていることの方が多いんですが、もっと力がついてきたら先輩や後輩を助けられるようになりたいです。年代は20代~50代と幅広いですが、思ったことは割と上の人にもぶつけている印象で、発言しやすいなと感じます。テレワークが中心でほとんど出社することはないですが、オンラインで雑談できたりするので、寂しくなることもありません。また、今はTypeScriptという言語の勉強会をグループ中心でやっていて、みんなで学ぼうという姿勢がいいなと思います。雰囲気ではないですが、残業時間は平均20時間くらいで少なめで、総合的に見てとても働きやすいです。 ブログを見てくださった方に向けて このブログ記事は学生に向けた働き方紹介ということだったんですが、もしかしたら学生以外の方も見てくださっているかもしれません。セキュリティってちょっと気になるな、という方がいたら、ぜひカジュアル面談などを受けてみてください。グループの雰囲気などはやはり実際にメンバーに会ってみないと分からなかったりするし、こちらももっとここに書ききれていない弊社の魅力を伝えられたらと思います。 学生の方に向けたメッセージとしては、自分がいちばん何をしている時に感動したり、またこれをやってみたい!と思うかを大事にしてほしいです。イメージで会社を選ぶのではなく、その感動したことを仕事にできる会社を選ぶと、仕事をしていても楽しいなと思うことが多いはずです。自分の例で言うと、プログラミングしたものが自分の思い通りに動くとすごく感動するし、またコードを書いてみたい!と思います。また、攻撃者の目線に立ってハッキングしてみようとデモアプリにハッキングしたり、この攻撃を防ぐには・・・?と考えだすと、面白い!!となります。会社のイメージではなく、仕事内容をぜひ重視して会社を選んでみてください。もし、ワクワクどきどきするのがセキュリティという分野だったら、ぜひ一緒に働きましょう!! www.isid.co.jp 中途の方向け:セキュリティエンジニア(セキュリティ設計) 執筆: @onishi.mayu 、レビュー: @yamada.y ( Shodo で執筆されました )
アバター
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回のテーマは、Stable Diffusion 2.1-金髪美女写真です。 Stability AI が Prompt Bookという呪文の解説スライド を出しているので、それを研究します。今回は、 Prompt Book の Photorealism (写真のようなリアルな描写法)の金髪美女の呪文を解説します。解説スライドでは、14, 15ページの内容になります。 Prompt Book は更新が入っているようなので、最新版では、内容が変わっている可能性もありますが、本質的な部分は変わっていないはずです。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文 美少女アニメ画 美少女写真 女性イラスト 長い呪文は切り捨てられる AUTOMATIC1111のインストール AUTOMATIC1111のセッティング 金髪美女写真の出力結果 金髪美女写真の通常呪文 portrait of a blonde woman in a white dress posing for a picture tumblr digital art glowing with colored light ethereal lighting forest ray light taken in 2022 handsome girl brooke ashling diffused natural skin glow 金髪美女写真のネガティブ呪文 gingerbread candy village, on a plate in a busy diner ice - t drinking beer and laughing cartoon, 1990s cartoon black show room breaking bad scene animal shaped bread broken down grey wall cheese and pepperoni black hair duplicate 仲間募集 Stable Diffusionの全コンテンツ AUTOMATIC1111のインストール Stable Diffusion 2.1 を実行する環境として、 AUTOMATIC1111 を使います。 AUTOMATIC1111 は、 Stable Diffusion を Google Colab 直接ではなく、 UI 経由で実行できるようにしています。似たような機能のものはいくつかありますが、 AUTOMATIC1111 は人気が高く、情報量も多いのでおすすめです。 Google Colab で動かすための公式のノートブックは こちら になります。 このノートブックを実行すると、下の方に Running on public URL: https://2c76db068be0e79a.gradio.app のようなリンクが表示されるので、クリックしましょう。 AUTOMATIC1111のセッティング パラメータは以下のようになります。 Sampling Steps: 50 Sampling Method: DPM2 Width: 768 Height: 768 CFG Scale: 7.5 Seed: -1 Sampling Stepsは、デフォルトの 20 だと少ないので、 50 くらいにしておきましょう。 Sampling Methodは DPM2 をお勧めしますが、 DDIM も悪くはありません。 Width と Height は 768 をお勧めします。今回使っているモデルは、 768 用のためです。 512 だとかを使うと画像が崩れることがあります。 CFG Scale は入力した呪文にどれだけ近い画像を生成するかのパラメータです。デフォルトの 7 でも問題ありませんが、僕はなんとなく 7.5 を指定しています。 金髪美女写真の出力結果 金髪美女写真の出力結果の例です。 金髪美女写真の通常呪文 金髪美女写真の通常呪文は次のようになります。 portrait of a blonde woman in a white dress posing for a picture, tumblr, digital art, glowing with colored light, ethereal lighting, forest ray light, taken in 2022, handsome girl, brooke ashling, diffused natural skin glow 見やすいように改行すると次のようになります。 portrait of a blonde woman in a white dress posing for a picture, tumblr, digital art, glowing with colored light, ethereal lighting, forest ray light, taken in 2022, handsome girl, brooke ashling, diffused natural skin glow 個々の呪文を解説しましょう。 portrait of a blonde woman portrait of a blonde woman は、「金髪女性の 肖像画 」という意味です。 in a white dress in a white dress は、「白いドレスを着て」という意味です。 posing for a picture posing for a picture は、「写真撮影のためにポーズを取る」という意味です。正面から撮った棒立ちの写真はつまらないので、この呪文は覚えておきましょう。 tumblr tumblr は、 SNS の一つです。指定するとなんとなくクオリティが上がる気がする呪文です。たぶん、外してもそれほど違いはないと思います。 digital art digital art は、「デジタルアート」という意味です。たぶん、外してもそれほど違いはないと思います。 glowing with colored light glowing with colored light は、「色付きの光で輝く」という意味です。この効果は、金髪女性に影響します。 ethereal lighting ethereal lighting は、「優美な照明」の意味です。 forest ray light forest ray light は、「森の光」という意味です。木々の間から木漏れ日が見えるような効果になることが多いです。単なる forest よりずっと美しくなるので、この呪文は覚えておきましょう。 taken in 2022 taken in 2022 は、「2022年に撮られた」という意味です。たぶん、外してもそれほど違いはないと思います。 handsome girl handsome girl は、「キリッとして目鼻立ちの整った少女」の意味です。 brooke ashling brooke ashling は、人の名前のような気がしますが、なんの意味があるのかわかりません。たぶん、外してもそれほど違いはないと思います。 diffused natural skin glow diffused natural skin glow は、「分散した自然な肌の輝き」の意味です。 金髪美女写真のネガティブ呪文 通常呪文を打ち消すのがネガティブ呪文です。 金髪美女写真のネガティブ呪文は次のようになります。 gingerbread candy village, on a plate in a busy diner, ice - t, drinking beer and laughing, cartoon, 1990s cartoon, black show room, breaking bad scene, animal - shaped bread, broken down grey wall, cheese and pepperoni, black hair, duplicat 見やすいように改行すると次のようになります。 gingerbread candy village, on a plate in a busy diner, ice - t, drinking beer and laughing, cartoon, 1990s cartoon, black show room, breaking bad scene, animal - shaped bread, broken down grey wall, cheese and pepperoni, black hair, duplicate 個々の呪文を解説しましょう。 gingerbread candy village, on a plate in a busy diner 結構意味不明ですが、「繁盛している食堂の皿の上のお菓子」くらいの意味でしょうか。通常呪文とほとんど関係がないので、たぶん、外してもそれほど違いはないと思います。 ice - t これも意味不明ですが、「アイス ティー 」のことなのかもしれません。通常呪文とほとんど関係がないので、たぶん、外してもそれほど違いはないと思います。 drinking beer and laughing drinking beer and laughing は、「ビールを飲んで笑う」の意味です。通常呪文とほとんど関係がないので、たぶん、外してもそれほど違いはないと思います。 cartoon, 1990s cartoon cartoon は、「漫画」、 1990s cartoon は、「1990年代の漫画」の意味です。リアルな写真が目的なので、漫画の要素を打ち消しているのでしょう。 black show room black show room は、「黒い展示室」の意味です。通常呪文とほとんど関係がないので、たぶん、外してもそれほど違いはないと思います。 breaking bad scene breaking bad scene は、「ハメを外したシーン」の意味です。ハメを外したシーンを打ち消します。 animal animal は、「動物」の意味です。動物を打ち消します。 shaped bread shaped bread は、「成形されたパン」の意味です。通常呪文とほとんど関係がないので、たぶん、外してもそれほど違いはないと思います。 broken down grey wall broken down grey wall は、「壊れた灰色の壁」の意味です。たまに、背景が灰色になってしまうことがあるので、それを打ち消しているんだと思いますが、打ち消しきれていない気がします。 cheese and pepperoni cheese and pepperoni は、「チーズとパペロニ」の意味です。通常呪文とほとんど関係がないので、たぶん、外してもそれほど違いはないと思います。 black hair black hair は、「黒い髪」の意味です。金髪にするために、黒い髪を打ち消します。 duplicate duplicate は、「複製」の意味です。体のパーツが複製されるのを打ち消したいんじゃないかと思います。例えば、鼻が2つあるとか。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの全コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
アバター
電通国際情報サービス 、オープン イノベーション ラボの 比嘉康雄 です。 Stable Diffusionシリーズ、今回のテーマは、Stable Diffusion 2.1-金髪美女写真です。 Stability AI が Prompt Bookという呪文の解説スライド を出しているので、それを研究します。今回は、 Prompt Book の Photorealism (写真のようなリアルな描写法)の金髪美女の呪文を解説します。解説スライドでは、14, 15ページの内容になります。 Prompt Book は更新が入っているようなので、最新版では、内容が変わっている可能性もありますが、本質的な部分は変わっていないはずです。 Stable Diffusionのおすすめコンテンツはこちら。 Waifu Diffusion 1.3.5_80000 v2.1 金髪美女写真 v2.1 美少女アニメ画 v2.1 AUTOMATIC1111 v2.0 美少女イラスト v1.5 美少女画検証 美少女アニメ画改善版 美少女を高確率で出す呪文 美少女アニメ画 美少女写真 女性イラスト 長い呪文は切り捨てられる AUTOMATIC1111のインストール AUTOMATIC1111のセッティング 金髪美女写真の出力結果 金髪美女写真の通常呪文 portrait of a blonde woman in a white dress posing for a picture tumblr digital art glowing with colored light ethereal lighting forest ray light taken in 2022 handsome girl brooke ashling diffused natural skin glow 金髪美女写真のネガティブ呪文 gingerbread candy village, on a plate in a busy diner ice - t drinking beer and laughing cartoon, 1990s cartoon black show room breaking bad scene animal shaped bread broken down grey wall cheese and pepperoni black hair duplicate 仲間募集 Stable Diffusionの全コンテンツ AUTOMATIC1111のインストール Stable Diffusion 2.1 を実行する環境として、 AUTOMATIC1111 を使います。 AUTOMATIC1111 は、 Stable Diffusion を Google Colab 直接ではなく、 UI 経由で実行できるようにしています。似たような機能のものはいくつかありますが、 AUTOMATIC1111 は人気が高く、情報量も多いのでおすすめです。 Google Colab で動かすための公式のノートブックは こちら になります。 このノートブックを実行すると、下の方に Running on public URL: https://2c76db068be0e79a.gradio.app のようなリンクが表示されるので、クリックしましょう。 AUTOMATIC1111のセッティング パラメータは以下のようになります。 Sampling Steps: 50 Sampling Method: DPM2 Width: 768 Height: 768 CFG Scale: 7.5 Seed: -1 Sampling Stepsは、デフォルトの 20 だと少ないので、 50 くらいにしておきましょう。 Sampling Methodは DPM2 をお勧めしますが、 DDIM も悪くはありません。 Width と Height は 768 をお勧めします。今回使っているモデルは、 768 用のためです。 512 だとかを使うと画像が崩れることがあります。 CFG Scale は入力した呪文にどれだけ近い画像を生成するかのパラメータです。デフォルトの 7 でも問題ありませんが、僕はなんとなく 7.5 を指定しています。 金髪美女写真の出力結果 金髪美女写真の出力結果の例です。 金髪美女写真の通常呪文 金髪美女写真の通常呪文は次のようになります。 portrait of a blonde woman in a white dress posing for a picture, tumblr, digital art, glowing with colored light, ethereal lighting, forest ray light, taken in 2022, handsome girl, brooke ashling, diffused natural skin glow 見やすいように改行すると次のようになります。 portrait of a blonde woman in a white dress posing for a picture, tumblr, digital art, glowing with colored light, ethereal lighting, forest ray light, taken in 2022, handsome girl, brooke ashling, diffused natural skin glow 個々の呪文を解説しましょう。 portrait of a blonde woman portrait of a blonde woman は、「金髪女性の 肖像画 」という意味です。 in a white dress in a white dress は、「白いドレスを着て」という意味です。 posing for a picture posing for a picture は、「写真撮影のためにポーズを取る」という意味です。正面から撮った棒立ちの写真はつまらないので、この呪文は覚えておきましょう。 tumblr tumblr は、 SNS の一つです。指定するとなんとなくクオリティが上がる気がする呪文です。たぶん、外してもそれほど違いはないと思います。 digital art digital art は、「デジタルアート」という意味です。たぶん、外してもそれほど違いはないと思います。 glowing with colored light glowing with colored light は、「色付きの光で輝く」という意味です。この効果は、金髪女性に影響します。 ethereal lighting ethereal lighting は、「優美な照明」の意味です。 forest ray light forest ray light は、「森の光」という意味です。木々の間から木漏れ日が見えるような効果になることが多いです。単なる forest よりずっと美しくなるので、この呪文は覚えておきましょう。 taken in 2022 taken in 2022 は、「2022年に撮られた」という意味です。たぶん、外してもそれほど違いはないと思います。 handsome girl handsome girl は、「キリッとして目鼻立ちの整った少女」の意味です。 brooke ashling brooke ashling は、人の名前のような気がしますが、なんの意味があるのかわかりません。たぶん、外してもそれほど違いはないと思います。 diffused natural skin glow diffused natural skin glow は、「分散した自然な肌の輝き」の意味です。 金髪美女写真のネガティブ呪文 通常呪文を打ち消すのがネガティブ呪文です。 金髪美女写真のネガティブ呪文は次のようになります。 gingerbread candy village, on a plate in a busy diner, ice - t, drinking beer and laughing, cartoon, 1990s cartoon, black show room, breaking bad scene, animal - shaped bread, broken down grey wall, cheese and pepperoni, black hair, duplicat 見やすいように改行すると次のようになります。 gingerbread candy village, on a plate in a busy diner, ice - t, drinking beer and laughing, cartoon, 1990s cartoon, black show room, breaking bad scene, animal - shaped bread, broken down grey wall, cheese and pepperoni, black hair, duplicate 個々の呪文を解説しましょう。 gingerbread candy village, on a plate in a busy diner 結構意味不明ですが、「繁盛している食堂の皿の上のお菓子」くらいの意味でしょうか。通常呪文とほとんど関係がないので、たぶん、外してもそれほど違いはないと思います。 ice - t これも意味不明ですが、「アイス ティー 」のことなのかもしれません。通常呪文とほとんど関係がないので、たぶん、外してもそれほど違いはないと思います。 drinking beer and laughing drinking beer and laughing は、「ビールを飲んで笑う」の意味です。通常呪文とほとんど関係がないので、たぶん、外してもそれほど違いはないと思います。 cartoon, 1990s cartoon cartoon は、「漫画」、 1990s cartoon は、「1990年代の漫画」の意味です。リアルな写真が目的なので、漫画の要素を打ち消しているのでしょう。 black show room black show room は、「黒い展示室」の意味です。通常呪文とほとんど関係がないので、たぶん、外してもそれほど違いはないと思います。 breaking bad scene breaking bad scene は、「ハメを外したシーン」の意味です。ハメを外したシーンを打ち消します。 animal animal は、「動物」の意味です。動物を打ち消します。 shaped bread shaped bread は、「成形されたパン」の意味です。通常呪文とほとんど関係がないので、たぶん、外してもそれほど違いはないと思います。 broken down grey wall broken down grey wall は、「壊れた灰色の壁」の意味です。たまに、背景が灰色になってしまうことがあるので、それを打ち消しているんだと思いますが、打ち消しきれていない気がします。 cheese and pepperoni cheese and pepperoni は、「チーズとパペロニ」の意味です。通常呪文とほとんど関係がないので、たぶん、外してもそれほど違いはないと思います。 black hair black hair は、「黒い髪」の意味です。金髪にするために、黒い髪を打ち消します。 duplicate duplicate は、「複製」の意味です。体のパーツが複製されるのを打ち消したいんじゃないかと思います。例えば、鼻が2つあるとか。 仲間募集 私たちは同じグループで共に働いていただける仲間を募集しています。 現在、以下のような職種を募集しています。 ソリューションアーキテクト AIエンジニア Stable Diffusionの全コンテンツ 人物写真編 レンズ編 画像タイプ編 美少女アニメ画編 美少女写真編 女性イラスト編 美しい夜空を見渡す男編 魅惑的な女アニメ画(トゥーンレンダリング)編 美少女を高確率で出す呪文編 長い呪文は切り捨てられる編 蒸気機関が高度に発達したレトロなアニメ(スチームパンク)の世界観編 A as Bの呪文による画像合成編 かわいい動物の擬人化編 バベルの塔のイラスト編 TPU版の使い方 美少女アニメ画改善版 v1.5 美少女画検証 東京タワーの写真 折り紙合体変形ロボ v2.0 美少女イラスト v2.1 AUTOMATIC1111 v2.1 美少女アニメ画 v2.1 金髪美女写真 Waifu Diffusion 1.3.5_80000 執筆: @higa 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
アバター
こんにちは、X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループ 新卒入社2年目の大西です。これは、 電通国際情報サービス Advent Calendar 2022 12/21の記事です。今日は、私が所属するセキュリティグループについて記事を書いてみました!配属されて1年余りですが、グループの仕事内容や雰囲気、魅力などをお伝えできればと思います。 どうしてセキュリティグループに入ったの?? どんな仕事をしているの?? やりがいや達成感は?? 大変なところは?? グループの雰囲気はどんなかんじ?? ブログを見てくださった方に向けて どうしてセキュリティグループに入ったの?? 昨年セキュリティグループに配属された際、いろんな人に「どうしてセキュリティの仕事がしたいの?」と聞かれました。私は、入社する前からセキュリティに興味があり、セキュリティに関わる仕事がしたいと思っていました。その理由は、いくつかあります。 セキュリティの仕事=必要な仕事 世の中にはいろんな便利なものがありますが、便利なだけでは人は満足しないと思います。便利さ以前に、「安心」がないと、誰もそれを使ってくれません。例えば、音楽のストリーミングサービスで、いろんな曲が定額で聴き放題だけど、クレジットカードなどの支払い情報が外部に ダダ漏れ だったら誰もそれを使いたいとは思いません。 どんな便利なアプリやシステムでも絶対に必要なもの...安心とかセキュリティとか...それらを守る仕事がしたいとずっと思っていて、それを新人の時に人事の方に伝え、無事セキュリティグループに配属されました!(ISIDは、できるだけ希望の部署に入れてあげたいという思いの強い会社だと思います。) セキュリティエンジニア、足りてない... ニュースで セキュリティインシデント を目にすることはたまにしかないかもしれませんが、 セキュリティインシデント は毎日起こっています。 Security Next というサイトでは、情報セキュリティに関するニュースを日刊で届けてくれるのですが、これを見ていると本当に毎日インシデントだらけだなぁと思います。しかし、それらインシデントに対応できるようなセキュリティエンジニアは不足していて、多くの企業で課題となっています。セキュリティ人材が足りていない中で、少しでも貢献したいという気持ちがありました。今年もセキュリティグループに新たに4人の方が採用されて、やはりニーズは高まっているんだなと感じます。 セキュリティエンジニア、かっこいい! 私がセキュリティに興味を持ち始めたのは、ハッキングをしている人たちをTVで見て、ハッキングって面白そう!と思ったところからでした。そこから、 IPUSIRON さんの 「ハッキング・ラボの作り方」 を読みながら簡単なハッキングをしてみたり、CTF(Capture The Flag)と呼ばれるセキュリティコンテストの問題を解いてみたりして、ますます面白いと思うようになりました。 ハッキングの知識を悪いことに使えば犯罪ですが、いいことに使えば世の中のいろんなシステムを攻撃から守れます。セキュリティエンジニアは、プログラミングの知識だけでなく、ネットワーク、暗号化・認証技術、 クラウド 技術など様々なことに精通している必要があります。システムやアプリに存在するセキュリティの穴をどう塞ぐか、いろんな知識を持ち寄って考えなければなりません。それが、大変ではあるけれど、とてもかっこいいなと感じます! どんな仕事をしているの?? 現在9名のメンバーがセキュリティグループに所属し、部門横断で システム開発 ・運用におけるセキュリティを確保する支援を行っています。SRB(Security Review Board), SOC(Security Operation Center), IRG(Incident Response Group)という3つのグループがあり、図にすると以下のような感じです。 これまではSRB中心の業務でしたが、今年SOCとIRGチームが立ち上がりました。それぞれのグループの業務内容については以下のとおりです。 SRB: ISIDの事業部が担当している案件においてセキュリティに関する懸念事項を確認し、リリース前までに修正した状態にします。具体的には、案件の提案段階や設計段階でセキュリティに関する懸念事項がないか確認したり、セキュリティツールを用いた検査を行ったりし、事業部側に改善案を提案します。 SOC: クラウド 環境のセキュリティ対策を可視化し、被害の拡大を防止して問題の早期検知を行います。各案件のセキュリティ設定基準に対する準拠状況を把握し、チェックした結果を案件担当に伝え、改善を進めていきます。脅威検知ではアラートに対して、案件担当と連携し問題調査を行います。また、案件担当者に対し、 クラウド 専門の部署と合同でセキュリティ勉強会を行ったりもします。 IRG: セキュリティインシデント が発生した際に、状況を把握して被害の拡大を防止したり、被害の範囲を特定するなど適切に事後対応を行います。また、インシデントが起きた原因を突き止めそれを教訓化することで、将来のインシデントのリスクを低減させます。 この中で私はSRBチームに所属しており、セキュリティツールを用いた 脆弱性 検査(動的検査)の担当しています(実は、担当になったばかりで先月キャッチアップを終え、実際に案件を担当するのはこれからです!)。他にも、社内向けWeb アプリ開発 や、新人研修のセキュリティ講義、また、ISIDで使用しているソフトウェアやハードウェアなどの 脆弱性 が公開されればそれを全社に通知する業務なども担当しています。来年からは、セキュリティツールでは見つけにくい 脆弱性 を発見するため、マニュアル検査が本格的に始まります。実際に擬似的な攻撃コードなどを投げ、返ってきたレスポンスを見て脆弱かどうか検査していくのですが、経験と専門性がより問われるので、これから頑張って技術を習得していきたいと思います! やりがいや達成感は?? 業務によってやりがいや達成感を感じる瞬間は違いますが、例えばWeb アプリ開発 では、一つの機能を作り終えた時にすごくやりがいを感じますし、新人研修のセキュリティ講義をしたときは、講義やハッキング演習を通して新人がセキュリティに興味を持ってくれた時にもすごくやりがいを感じました。 これから 脆弱性 検査を本格的に担当していくと、リリース前に 脆弱性 が見つけられた時などにも、 セキュリティインシデント を未然に防ぐことができて大きなやりがいを感じるのではないかと思います。セキュリティ要件の高い案件や大きな規模の案件のセキュリティレビューや 脆弱性 検査をすることもあり、金融機関や官公庁の案件を担当することもあります。それらの案件のシステムがもし サイバー攻撃 を受けると、影響を受ける範囲がとても大きくなるので、それを未然に防ぐ仕事というのはとてもやりがいがあるのではないでしょうか。 大変なところは?? 身に付けなければならない知識・スキルが多いことです。それは最初から覚悟していたことなのですが、覚えても覚えても新しい技術が出てくるので、心が折れそうになることもあります。しかし、一歩ずつやっていくしかないと思うので、毎日の業務で新しいことを覚えたり、資格の勉強をしたりして地道に知識を増やしていきます。資格の勉強は、体系的にセキュリティのことを学べるのでいいなと思います。ISIDは資格試験や研修の費用を会社がお金を出してくれるのでとてもありがたいです。資格勉強に限らずですが、勉強を進めていると、初め聞いた時はよくわからなかった技術も他に新しいことを覚えていくうちに、あ、あの時のあれはそういうことだったんだ!!みたいな、点と点が繋がる瞬間がたまにあって、自分の成長を感じる瞬間がとても嬉しいです。この仕事は、本当に日々勉強です。 グループの雰囲気はどんなかんじ?? セキュリティグループの雰囲気は、「個」というよりは「チーム」、「上下関係」というよりは「フラット」、「静か」というよりは「にぎやか」という感じです。みんなで助け合いという感じで、今は助けられていることの方が多いんですが、もっと力がついてきたら先輩や後輩を助けられるようになりたいです。年代は20代~50代と幅広いですが、思ったことは割と上の人にもぶつけている印象で、発言しやすいなと感じます。テレワークが中心でほとんど出社することはないですが、オンラインで雑談できたりするので、寂しくなることもありません。また、今はTypeScriptという言語の勉強会をグループ中心でやっていて、みんなで学ぼうという姿勢がいいなと思います。雰囲気ではないですが、残業時間は平均20時間くらいで少なめで、総合的に見てとても働きやすいです。 ブログを見てくださった方に向けて このブログ記事は学生に向けた働き方紹介ということだったんですが、もしかしたら学生以外の方も見てくださっているかもしれません。セキュリティってちょっと気になるな、という方がいたら、ぜひカジュアル面談などを受けてみてください。グループの雰囲気などはやはり実際にメンバーに会ってみないと分からなかったりするし、こちらももっとここに書ききれていない弊社の魅力を伝えられたらと思います。 学生の方に向けたメッセージとしては、自分がいちばん何をしている時に感動したり、またこれをやってみたい!と思うかを大事にしてほしいです。イメージで会社を選ぶのではなく、その感動したことを仕事にできる会社を選ぶと、仕事をしていても楽しいなと思うことが多いはずです。自分の例で言うと、プログラミングしたものが自分の思い通りに動くとすごく感動するし、またコードを書いてみたい!と思います。また、攻撃者の目線に立ってハッキングしてみようとデモアプリにハッキングしたり、この攻撃を防ぐには・・・?と考えだすと、面白い!!となります。会社のイメージではなく、仕事内容をぜひ重視して会社を選んでみてください。もし、ワクワクどきどきするのがセキュリティという分野だったら、ぜひ一緒に働きましょう!! www.isid.co.jp 中途の方向け:セキュリティエンジニア(セキュリティ設計) 執筆: @onishi.mayu 、レビュー: @yamada.y ( Shodo で執筆されました )
アバター
みなさんこんにちは! ISID 金融ソリューション事業部、新卒2年目の松崎です。 本記事は 電通国際情報サービス Advent Calendar 2022 12月 20日 の記事になります。 今回は、フォトグラメトリソフトウェアの RealityCapture と ゲームエンジン の UnrealEngine を使って 現実空間の物体から作成した3Dモデルを仮想空間に設置する手順 を紹介します。 はじめに 「 メタバース 」とは、どのようなものでしょうか。 語源を調べると、超越・高次を意味する「Meta」と世界を意味する「 Universe 」を組み合わせた造語であると分かります。 つまり、 超越した世界 と解釈できるわけですが、なかなか抽象的で理解し辛いですね。 実際のところ メタバース の正確な定義は定まっておらず、現状は「インターネット上に存在する3D仮想空間」と理解しておけば問題ないと考えています。 今回は、そんな メタバース の構築ツールであるUnrealEngineのマップに現実空間の物体を置き、リアルタイム レンダリング してみた! ということでその実施手順を紹介します。 手順 RealityCaptureを用いて、現実空間の物体から3Dモデルを作成する 作成した3DモデルをFBX形式でエクスポートし、UnrealEngineへインポートする UnrealEngineのマップ上に、3Dモデルを設置する 使用機材 PC(3Dモデル構築、仮想空間構築) OS:Windows10 Home CPU: Intel Core i9 -10900K   @3.70GHz  GPU : NVIDIA GeForce RTX 3080 RealityCapture 1.2.1 インストール済み UnrealEngine 5.1.0 インストール済み iPhone12mini(写真撮影) OS: iOS 15.6 ZY Cami 1.3.4(写真アプリ)インストール済み Zhiyun Smooth Q4( スマートフォン 用ジンバル、三脚) 手順1.現実空間の物体から3D モデルを作成 まず初めに、 RealityCapture を用いて3Dモデルを作成します。 RealityCapture は、 Epic Games 社が提供するフォトグラメトリソフトウェアです。 写真から3Dモデルを作成することを強みとしており、仕上がり品質の高さが特徴的です。 また、LiDARなどによるレーザースキャンデータとの組み合わせも可能で、地形の測量等にも活用されています。 スマートフォン 用無料アプリもリリースされましたので、ご興味ある方は是非お試しください! 本記事ではPC版RealityCaptureを使用し、 写真だけを用いて 3Dモデル作成を行います。 ①写真撮影 高精細な3Dモデルを作成する場合、インプットに使う写真の質が重要になります。 写真の質を上げる方法として、今回は以下の観点を意識しました。 撮影方法:対象物体を取り囲むように周回移動しながら撮影 画質:iPhone12mini 最高画質(12MP) 画像重なり:前後の画像にて、70%以上の重なりを維持(計140枚撮影) ブレ軽減:三脚とジンバルを用いて、撮影時のブレを最小化 距離:可能な限り対象物体に近づいて撮影(写真内で物体が占める ピクセル 数を最大化) また、カメラ設定は以下になります。 ISO 100 ズーム:等倍(1.0) 写真サイズ:2750 × 3664 (縦撮り) ②RealityCaptureで3Dモデル構築 撮影した写真をRealityCaptureへインポートし、3Dモデルを作成します。 2.1 写真を取込み、アライメントを実施(アライメント処理時間:1分) アライメントとは、オーバーラップ撮影された写真から、複数の写真の共通点を点として抽出し、低密度の点群データを作成する処理になります。同時にカメラの位置や対象物との距離を計算しており、下記画像の白い小さな四角形は、写真1枚1枚の撮影位置推定結果を示しています。 今回は三脚を用いて高さ固定で撮影をしていますが、RealityCapture上においても高さが一定であると推定されています。 2.2 コン トロール ポイントを用いて距離を定義し、再アライメント 最初のアライメント時に物体の大きさが自動定義されますが、ズレが大きい時は自分で再定義します。 再定義の際はコン トロール ポイントを利用します。コン トロール ポイントとは、写真上に目印となるポイントを設定できる機能です。アライメント処理で写真同士のオーバーラップ判定が上手く行かなかった時などに、写真間の共通ポイントをコン トロール ポイントとして指定することで、オーバーラップ判定の手助けを行うことができます。 また、コン トロール ポイントを2つ設定し、2点間の距離を再定義することで大きさを再定義できます。(定義後、アライメントを再実施する必要があります。) 今回は自動定義で11.6mとなってしまっていた為、おおよその実測値として0.2mを再定義しました。 2.3 メッシュモデルを作成 RealityCaptureではメッシュモデル作成時に、モデル解像度のオプションとして「通常精細」「高精細」を選ぶことができます。それぞれで作成したメッシュモデルを以下に示します。  ・通常精細(所要時間:6分 ポリゴン数:330万)  ・高精細(所要時間:23分 ポリゴン数:1360万) 通常精細でも綺麗なメッシュモデルが出来ていますが、高精細ではさらに細かい凹凸まで再現されていることが確認出来ます。以降は、高精細モデルを用いてモデル構築を行います。 2.4 テクスチャを作成 テクスチャ作成時の設定は以下になります。 テクスチャ最大解像度:16K×16K スタイル:テクセルサイズを固定 テクセルサイズ:最適化 以下に、上記設定にてテクスチャを貼りつけた3Dモデルを示します。 作成した3Dモデルと、インプットに用いた写真を並べてみます。 元画像と比較しても遜色ない程の高精細3Dモデルが作成出来ました。 (一方、左の枝が切れていたり花の周辺に茶色の端部分が残ったりと、何点か微修正の必要な箇所が見受けられます。 本記事では割愛しますが、このような箇所は Blender 等の3DCGツールを用いて対処します。) 手順2.作成した3DモデルをFBX形式でエクスポートし、UnrealEngineへインポートする ①RealityCaptureからのエクスポート 3DモデルをRealityCaptureからエクスポートします。 エクスポート形式はFBXを指定します。 エクスポート時の設定値は以下になります。 頂点法線のエクスポート:はい 頂点カラーのエクスポート:いいえ テクスチャのエクスポート:はい 座標系:グリッド平面 変換プリセット:カスタム XYZ移動:0 XYZ回転:0 XYZスケール:100 (UEインポート時に適切な大きさに変換する為) X座標の反転:いいえ YZ座標の反転:はい(UEインポート時に正面を向かせる為) 下記のように3Dモデルがエクスポートされます。 ②UnrealEngineへインポート 3Dモデルを取込むための仮想空間を構築します。 本記事では、建築用のBlankプリセットを用いて仮想空間構築を行いました。 続いてモデルのインポートを行います。 3Dモデルのインポート先フォルダを作成し、モデルファイルをドロップします。 インポート時の設定値は以下になります。 Skeltal Mesh:OFF Build Nanaite:ON Generate Missing Collision:ON Convert Scene:ON Force Front XAxis:OFF Convert Scene Unit:OFF 下記のように3Dモデルがインポートされます。 手順3.UnrealEngineのマップ上に、3Dモデルを配置する マテリアルのベー スカラー にテクス チャフ ァイルを接続し、UnrealEngine上の3Dモデルにテクスチャを貼りつけます。 3Dモデルを ドラッグ&ドロップ し、マップ上へ配置します。 以下は、配置した3Dモデルを一人称視点で確認したものです。 3Dモデルのみならず、影まで正確に表示されていることが確認出来ます。(影はUnrealEngineの機能です) 一方、RealityCapture上での3Dモデルと比べると全体的にツヤが失われており、花が固まっているように見えます。 RealityCaptureのアウトプット設定やUnrealEngineのインプット設定を見直し調整する必要がありそうです。 こちらに関しては、今後の課題となります。 Visualizationモードで見てみますと、3D モデルがリアルタイムで レンダリング されている様子を確認出来ます。 余談となりますが、UnrealEngine上で3Dモデルの明るさと粗さ(光の反射量)を調整してみます。 まず、明るさから調整します。 手順3.の最初では3Dマテリアルのベー スカラー とテクスチャを直接繋ぎましたが、この間に「Multiply」と「Blightness(Param)」を追加します。 これにより、「Blightness(Param)」に設定した値で明るさの調整ができるようになります。(デフォルト:1) 次に粗さです。 3D マテリアルのラフネスノードに「Roughness(Param)」を直接繋ぎます。 これにより、「Roughness(Param)」に設定した値で粗さの調整ができるようになります。(デフォルト:0.5) 調整準備が整いましたので、試しに以下の設定値にしてみます。 Blightness(Param):0.4 Roughness(Param):0.4 全体的に暗めな印象になりました。 仮想空間のスタイルに合わせて、自由に調整することが出来ます。 フォトグラメトリのデメリットとして、生成するテクスチャが、写真撮影時のライティングに影響を受けてしまうことが挙げられます。撮影時と違う印象にしたい場合は、上記手順を行うことで異なるライティング状態を再現できます。 おわりに 本記事では、フォトグラメトリを用いた3Dモデルの作成、またそれを仮想空間へ取り込んで設置する手順を紹介しました。UnrealEngineはゲーム用エンジンですが、RealityCapture等による高精細3Dモデルと組み合わせることにより” ノンゲーム ”領域での活用可能性が広がっていくと考えています。 また、今回は小さな物体を対象に3Dモデル化を行いましたが、次は大きな物体や部屋の内装/外装の3Dモデル化にも挑戦し、 ノンゲーム 領域でのフォトグラメトリ活用を目指します! ISIDの金融事業部では、金融直結の仕事に限らず様々な新規領域活動を行っており、本活動もその一端となります。また、現在ISIDでは web3領域のグループ横断組織 を立ち上げており、その中で メタバース やWeb3との連携も見据えた活動を進めていく予定です。 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ     参考資料 RealityCapture 日本語マニュアル - 前編- はじめてのRealityCapture-完全なモデルを作成する手順 RealityCapture to UnrealEngine5 lileaLab 執筆: @matsuzaki.shota 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
アバター
みなさんこんにちは! ISID 金融ソリューション事業部、新卒2年目の松崎です。 本記事は 電通国際情報サービス Advent Calendar 2022 12月 20日 の記事になります。 今回は、フォトグラメトリソフトウェアの RealityCapture と ゲームエンジン の UnrealEngine を使って 現実空間の物体から作成した3Dモデルを仮想空間に設置する手順 を紹介します。 はじめに 「 メタバース 」とは、どのようなものでしょうか。 語源を調べると、超越・高次を意味する「Meta」と世界を意味する「 Universe 」を組み合わせた造語であると分かります。 つまり、 超越した世界 と解釈できるわけですが、なかなか抽象的で理解し辛いですね。 実際のところ メタバース の正確な定義は定まっておらず、現状は「インターネット上に存在する3D仮想空間」と理解しておけば問題ないと考えています。 今回は、そんな メタバース の構築ツールであるUnrealEngineのマップに現実空間の物体を置き、リアルタイム レンダリング してみた! ということでその実施手順を紹介します。 手順 RealityCaptureを用いて、現実空間の物体から3Dモデルを作成する 作成した3DモデルをFBX形式でエクスポートし、UnrealEngineへインポートする UnrealEngineのマップ上に、3Dモデルを設置する 使用機材 PC(3Dモデル構築、仮想空間構築) OS:Windows10 Home CPU: Intel Core i9 -10900K   @3.70GHz  GPU : NVIDIA GeForce RTX 3080 RealityCapture 1.2.1 インストール済み UnrealEngine 5.1.0 インストール済み iPhone12mini(写真撮影) OS: iOS 15.6 ZY Cami 1.3.4(写真アプリ)インストール済み Zhiyun Smooth Q4( スマートフォン 用ジンバル、三脚) 手順1.現実空間の物体から3D モデルを作成 まず初めに、 RealityCapture を用いて3Dモデルを作成します。 RealityCapture は、 Epic Games 社が提供するフォトグラメトリソフトウェアです。 写真から3Dモデルを作成することを強みとしており、仕上がり品質の高さが特徴的です。 また、LiDARなどによるレーザースキャンデータとの組み合わせも可能で、地形の測量等にも活用されています。 スマートフォン 用無料アプリもリリースされましたので、ご興味ある方は是非お試しください! 本記事ではPC版RealityCaptureを使用し、 写真だけを用いて 3Dモデル作成を行います。 ①写真撮影 高精細な3Dモデルを作成する場合、インプットに使う写真の質が重要になります。 写真の質を上げる方法として、今回は以下の観点を意識しました。 撮影方法:対象物体を取り囲むように周回移動しながら撮影 画質:iPhone12mini 最高画質(12MP) 画像重なり:前後の画像にて、70%以上の重なりを維持(計140枚撮影) ブレ軽減:三脚とジンバルを用いて、撮影時のブレを最小化 距離:可能な限り対象物体に近づいて撮影(写真内で物体が占める ピクセル 数を最大化) また、カメラ設定は以下になります。 ISO 100 ズーム:等倍(1.0) 写真サイズ:2750 × 3664 (縦撮り) ②RealityCaptureで3Dモデル構築 撮影した写真をRealityCaptureへインポートし、3Dモデルを作成します。 2.1 写真を取込み、アライメントを実施(アライメント処理時間:1分) アライメントとは、オーバーラップ撮影された写真から、複数の写真の共通点を点として抽出し、低密度の点群データを作成する処理になります。同時にカメラの位置や対象物との距離を計算しており、下記画像の白い小さな四角形は、写真1枚1枚の撮影位置推定結果を示しています。 今回は三脚を用いて高さ固定で撮影をしていますが、RealityCapture上においても高さが一定であると推定されています。 2.2 コン トロール ポイントを用いて距離を定義し、再アライメント 最初のアライメント時に物体の大きさが自動定義されますが、ズレが大きい時は自分で再定義します。 再定義の際はコン トロール ポイントを利用します。コン トロール ポイントとは、写真上に目印となるポイントを設定できる機能です。アライメント処理で写真同士のオーバーラップ判定が上手く行かなかった時などに、写真間の共通ポイントをコン トロール ポイントとして指定することで、オーバーラップ判定の手助けを行うことができます。 また、コン トロール ポイントを2つ設定し、2点間の距離を再定義することで大きさを再定義できます。(定義後、アライメントを再実施する必要があります。) 今回は自動定義で11.6mとなってしまっていた為、おおよその実測値として0.2mを再定義しました。 2.3 メッシュモデルを作成 RealityCaptureではメッシュモデル作成時に、モデル解像度のオプションとして「通常精細」「高精細」を選ぶことができます。それぞれで作成したメッシュモデルを以下に示します。  ・通常精細(所要時間:6分 ポリゴン数:330万)  ・高精細(所要時間:23分 ポリゴン数:1360万) 通常精細でも綺麗なメッシュモデルが出来ていますが、高精細ではさらに細かい凹凸まで再現されていることが確認出来ます。以降は、高精細モデルを用いてモデル構築を行います。 2.4 テクスチャを作成 テクスチャ作成時の設定は以下になります。 テクスチャ最大解像度:16K×16K スタイル:テクセルサイズを固定 テクセルサイズ:最適化 以下に、上記設定にてテクスチャを貼りつけた3Dモデルを示します。 作成した3Dモデルと、インプットに用いた写真を並べてみます。 元画像と比較しても遜色ない程の高精細3Dモデルが作成出来ました。 (一方、左の枝が切れていたり花の周辺に茶色の端部分が残ったりと、何点か微修正の必要な箇所が見受けられます。 本記事では割愛しますが、このような箇所は Blender 等の3DCGツールを用いて対処します。) 手順2.作成した3DモデルをFBX形式でエクスポートし、UnrealEngineへインポートする ①RealityCaptureからのエクスポート 3DモデルをRealityCaptureからエクスポートします。 エクスポート形式はFBXを指定します。 エクスポート時の設定値は以下になります。 頂点法線のエクスポート:はい 頂点カラーのエクスポート:いいえ テクスチャのエクスポート:はい 座標系:グリッド平面 変換プリセット:カスタム XYZ移動:0 XYZ回転:0 XYZスケール:100 (UEインポート時に適切な大きさに変換する為) X座標の反転:いいえ YZ座標の反転:はい(UEインポート時に正面を向かせる為) 下記のように3Dモデルがエクスポートされます。 ②UnrealEngineへインポート 3Dモデルを取込むための仮想空間を構築します。 本記事では、建築用のBlankプリセットを用いて仮想空間構築を行いました。 続いてモデルのインポートを行います。 3Dモデルのインポート先フォルダを作成し、モデルファイルをドロップします。 インポート時の設定値は以下になります。 Skeltal Mesh:OFF Build Nanaite:ON Generate Missing Collision:ON Convert Scene:ON Force Front XAxis:OFF Convert Scene Unit:OFF 下記のように3Dモデルがインポートされます。 手順3.UnrealEngineのマップ上に、3Dモデルを配置する マテリアルのベー スカラー にテクス チャフ ァイルを接続し、UnrealEngine上の3Dモデルにテクスチャを貼りつけます。 3Dモデルを ドラッグ&ドロップ し、マップ上へ配置します。 以下は、配置した3Dモデルを一人称視点で確認したものです。 3Dモデルのみならず、影まで正確に表示されていることが確認出来ます。(影はUnrealEngineの機能です) 一方、RealityCapture上での3Dモデルと比べると全体的にツヤが失われており、花が固まっているように見えます。 RealityCaptureのアウトプット設定やUnrealEngineのインプット設定を見直し調整する必要がありそうです。 こちらに関しては、今後の課題となります。 Visualizationモードで見てみますと、3D モデルがリアルタイムで レンダリング されている様子を確認出来ます。 余談となりますが、UnrealEngine上で3Dモデルの明るさと粗さ(光の反射量)を調整してみます。 まず、明るさから調整します。 手順3.の最初では3Dマテリアルのベー スカラー とテクスチャを直接繋ぎましたが、この間に「Multiply」と「Blightness(Param)」を追加します。 これにより、「Blightness(Param)」に設定した値で明るさの調整ができるようになります。(デフォルト:1) 次に粗さです。 3D マテリアルのラフネスノードに「Roughness(Param)」を直接繋ぎます。 これにより、「Roughness(Param)」に設定した値で粗さの調整ができるようになります。(デフォルト:0.5) 調整準備が整いましたので、試しに以下の設定値にしてみます。 Blightness(Param):0.4 Roughness(Param):0.4 全体的に暗めな印象になりました。 仮想空間のスタイルに合わせて、自由に調整することが出来ます。 フォトグラメトリのデメリットとして、生成するテクスチャが、写真撮影時のライティングに影響を受けてしまうことが挙げられます。撮影時と違う印象にしたい場合は、上記手順を行うことで異なるライティング状態を再現できます。 おわりに 本記事では、フォトグラメトリを用いた3Dモデルの作成、またそれを仮想空間へ取り込んで設置する手順を紹介しました。UnrealEngineはゲーム用エンジンですが、RealityCapture等による高精細3Dモデルと組み合わせることにより” ノンゲーム ”領域での活用可能性が広がっていくと考えています。 また、今回は小さな物体を対象に3Dモデル化を行いましたが、次は大きな物体や部屋の内装/外装の3Dモデル化にも挑戦し、 ノンゲーム 領域でのフォトグラメトリ活用を目指します! ISIDの金融事業部では、金融直結の仕事に限らず様々な新規領域活動を行っており、本活動もその一端となります。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ     参考資料 RealityCapture 日本語マニュアル - 前編- はじめてのRealityCapture-完全なモデルを作成する手順 RealityCapture to UnrealEngine5 lileaLab 執筆: @matsuzaki.shota 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
アバター
みなさんこんにちは! ISID 金融ソリューション事業部、新卒2年目の松崎です。 本記事は 電通国際情報サービス Advent Calendar 2022 12月 20日 の記事になります。 今回は、フォトグラメトリソフトウェアの RealityCapture と ゲームエンジン の UnrealEngine を使って 現実空間の物体から作成した3Dモデルを仮想空間に設置する手順 を紹介します。 はじめに 「 メタバース 」とは、どのようなものでしょうか。 語源を調べると、超越・高次を意味する「Meta」と世界を意味する「 Universe 」を組み合わせた造語であると分かります。 つまり、 超越した世界 と解釈できるわけですが、なかなか抽象的で理解し辛いですね。 実際のところ メタバース の正確な定義は定まっておらず、現状は「インターネット上に存在する3D仮想空間」と理解しておけば問題ないと考えています。 今回は、そんな メタバース の構築ツールであるUnrealEngineのマップに現実空間の物体を置き、リアルタイム レンダリング してみた! ということでその実施手順を紹介します。 手順 RealityCaptureを用いて、現実空間の物体から3Dモデルを作成する 作成した3DモデルをFBX形式でエクスポートし、UnrealEngineへインポートする UnrealEngineのマップ上に、3Dモデルを設置する 使用機材 PC(3Dモデル構築、仮想空間構築) OS:Windows10 Home CPU: Intel Core i9 -10900K   @3.70GHz  GPU : NVIDIA GeForce RTX 3080 RealityCapture 1.2.1 インストール済み UnrealEngine 5.1.0 インストール済み iPhone12mini(写真撮影) OS: iOS 15.6 ZY Cami 1.3.4(写真アプリ)インストール済み Zhiyun Smooth Q4( スマートフォン 用ジンバル、三脚) 手順1.現実空間の物体から3D モデルを作成 まず初めに、 RealityCapture を用いて3Dモデルを作成します。 RealityCapture は、 Epic Games 社が提供するフォトグラメトリソフトウェアです。 写真から3Dモデルを作成することを強みとしており、仕上がり品質の高さが特徴的です。 また、LiDARなどによるレーザースキャンデータとの組み合わせも可能で、地形の測量等にも活用されています。 スマートフォン 用無料アプリもリリースされましたので、ご興味ある方は是非お試しください! 本記事ではPC版RealityCaptureを使用し、 写真だけを用いて 3Dモデル作成を行います。 ①写真撮影 高精細な3Dモデルを作成する場合、インプットに使う写真の質が重要になります。 写真の質を上げる方法として、今回は以下の観点を意識しました。 撮影方法:対象物体を取り囲むように周回移動しながら撮影 画質:iPhone12mini 最高画質(12MP) 画像重なり:前後の画像にて、70%以上の重なりを維持(計140枚撮影) ブレ軽減:三脚とジンバルを用いて、撮影時のブレを最小化 距離:可能な限り対象物体に近づいて撮影(写真内で物体が占める ピクセル 数を最大化) また、カメラ設定は以下になります。 ISO 100 ズーム:等倍(1.0) 写真サイズ:2750 × 3664 (縦撮り) ②RealityCaptureで3Dモデル構築 撮影した写真をRealityCaptureへインポートし、3Dモデルを作成します。 2.1 写真を取込み、アライメントを実施(アライメント処理時間:1分) アライメントとは、オーバーラップ撮影された写真から、複数の写真の共通点を点として抽出し、低密度の点群データを作成する処理になります。同時にカメラの位置や対象物との距離を計算しており、下記画像の白い小さな四角形は、写真1枚1枚の撮影位置推定結果を示しています。 今回は三脚を用いて高さ固定で撮影をしていますが、RealityCapture上においても高さが一定であると推定されています。 2.2 コン トロール ポイントを用いて距離を定義し、再アライメント 最初のアライメント時に物体の大きさが自動定義されますが、ズレが大きい時は自分で再定義します。 再定義の際はコン トロール ポイントを利用します。コン トロール ポイントとは、写真上に目印となるポイントを設定できる機能です。アライメント処理で写真同士のオーバーラップ判定が上手く行かなかった時などに、写真間の共通ポイントをコン トロール ポイントとして指定することで、オーバーラップ判定の手助けを行うことができます。 また、コン トロール ポイントを2つ設定し、2点間の距離を再定義することで大きさを再定義できます。(定義後、アライメントを再実施する必要があります。) 今回は自動定義で11.6mとなってしまっていた為、おおよその実測値として0.2mを再定義しました。 2.3 メッシュモデルを作成 RealityCaptureではメッシュモデル作成時に、モデル解像度のオプションとして「通常精細」「高精細」を選ぶことができます。それぞれで作成したメッシュモデルを以下に示します。  ・通常精細(所要時間:6分 ポリゴン数:330万)  ・高精細(所要時間:23分 ポリゴン数:1360万) 通常精細でも綺麗なメッシュモデルが出来ていますが、高精細ではさらに細かい凹凸まで再現されていることが確認出来ます。以降は、高精細モデルを用いてモデル構築を行います。 2.4 テクスチャを作成 テクスチャ作成時の設定は以下になります。 テクスチャ最大解像度:16K×16K スタイル:テクセルサイズを固定 テクセルサイズ:最適化 以下に、上記設定にてテクスチャを貼りつけた3Dモデルを示します。 作成した3Dモデルと、インプットに用いた写真を並べてみます。 元画像と比較しても遜色ない程の高精細3Dモデルが作成出来ました。 (一方、左の枝が切れていたり花の周辺に茶色の端部分が残ったりと、何点か微修正の必要な箇所が見受けられます。 本記事では割愛しますが、このような箇所は Blender 等の3DCGツールを用いて対処します。) 手順2.作成した3DモデルをFBX形式でエクスポートし、UnrealEngineへインポートする ①RealityCaptureからのエクスポート 3DモデルをRealityCaptureからエクスポートします。 エクスポート形式はFBXを指定します。 エクスポート時の設定値は以下になります。 頂点法線のエクスポート:はい 頂点カラーのエクスポート:いいえ テクスチャのエクスポート:はい 座標系:グリッド平面 変換プリセット:カスタム XYZ移動:0 XYZ回転:0 XYZスケール:100 (UEインポート時に適切な大きさに変換する為) X座標の反転:いいえ YZ座標の反転:はい(UEインポート時に正面を向かせる為) 下記のように3Dモデルがエクスポートされます。 ②UnrealEngineへインポート 3Dモデルを取込むための仮想空間を構築します。 本記事では、建築用のBlankプリセットを用いて仮想空間構築を行いました。 続いてモデルのインポートを行います。 3Dモデルのインポート先フォルダを作成し、モデルファイルをドロップします。 インポート時の設定値は以下になります。 Skeltal Mesh:OFF Build Nanaite:ON Generate Missing Collision:ON Convert Scene:ON Force Front XAxis:OFF Convert Scene Unit:OFF 下記のように3Dモデルがインポートされます。 手順3.UnrealEngineのマップ上に、3Dモデルを配置する マテリアルのベー スカラー にテクス チャフ ァイルを接続し、UnrealEngine上の3Dモデルにテクスチャを貼りつけます。 3Dモデルを ドラッグ&ドロップ し、マップ上へ配置します。 以下は、配置した3Dモデルを一人称視点で確認したものです。 3Dモデルのみならず、影まで正確に表示されていることが確認出来ます。(影はUnrealEngineの機能です) 一方、RealityCapture上での3Dモデルと比べると全体的にツヤが失われており、花が固まっているように見えます。 RealityCaptureのアウトプット設定やUnrealEngineのインプット設定を見直し調整する必要がありそうです。 こちらに関しては、今後の課題となります。 Visualizationモードで見てみますと、3D モデルがリアルタイムで レンダリング されている様子を確認出来ます。 余談となりますが、UnrealEngine上で3Dモデルの明るさと粗さ(光の反射量)を調整してみます。 まず、明るさから調整します。 手順3.の最初では3Dマテリアルのベー スカラー とテクスチャを直接繋ぎましたが、この間に「Multiply」と「Blightness(Param)」を追加します。 これにより、「Blightness(Param)」に設定した値で明るさの調整ができるようになります。(デフォルト:1) 次に粗さです。 3D マテリアルのラフネスノードに「Roughness(Param)」を直接繋ぎます。 これにより、「Roughness(Param)」に設定した値で粗さの調整ができるようになります。(デフォルト:0.5) 調整準備が整いましたので、試しに以下の設定値にしてみます。 Blightness(Param):0.4 Roughness(Param):0.4 全体的に暗めな印象になりました。 仮想空間のスタイルに合わせて、自由に調整することが出来ます。 フォトグラメトリのデメリットとして、生成するテクスチャが、写真撮影時のライティングに影響を受けてしまうことが挙げられます。撮影時と違う印象にしたい場合は、上記手順を行うことで異なるライティング状態を再現できます。 おわりに 本記事では、フォトグラメトリを用いた3Dモデルの作成、またそれを仮想空間へ取り込んで設置する手順を紹介しました。UnrealEngineはゲーム用エンジンですが、RealityCapture等による高精細3Dモデルと組み合わせることにより” ノンゲーム ”領域での活用可能性が広がっていくと考えています。 また、今回は小さな物体を対象に3Dモデル化を行いましたが、次は大きな物体や部屋の内装/外装の3Dモデル化にも挑戦し、 ノンゲーム 領域でのフォトグラメトリ活用を目指します! ISIDの金融事業部では、金融直結の仕事に限らず様々な新規領域活動を行っており、本活動もその一端となります。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ     参考資料 RealityCapture 日本語マニュアル - 前編- はじめてのRealityCapture-完全なモデルを作成する手順 RealityCapture to UnrealEngine5 lileaLab 執筆: @matsuzaki.shota 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
アバター
こんにちは。 XI本部 AIトランスフォーメーションセンター の徳原光です。ISID2022年 アドベントカレンダー の12月19日の記事を投稿します。 皆さん、仕事のいやしってありますか? お仕事にはストレスはつきもの。仕事の合間にメンタルをコン トロール する自分なりの方法があるって大事なことだと思うんですよね。 自分はこれです。 小さな温室に入った小さなジャングル。日々刻々と変化する熱帯植物の様子を眺めると、今頭の中でグルグル回っている悩みなんてどうでもいいなって気持ちになります。 温室を運用する上で一番大切なことは、当たり前ですが温度を保つことです(あと、湿度も大事)。なので、温室内の温度変化に注意しつつ、季節や天候によって設定温度を微妙に変更したくなるんですよね。 すでに、 Raspberry Pi に温湿度センサーDHT22を接続して温度遷移の記録を取りつつ、スマートプラグを利用して温室に設置したパネルヒーターの制御を行っていたのですが、パネルヒーターの稼働温度の変更や、温度遷移を確認するのにわざわざRsapberry Piに接続するのは面倒でした。 ということで個人で使用するために、 スマホ でかんたんに温度の監視やパネルヒーターの設定温度を変更するモバイルアプリを作成したので、そのレシピを紹介したいと思います。 同様のことに挑戦される方のご参考になればと思います。今回実装したコードはすべてこちらの リポジトリ に上げたので活用してください。 Pickerdot/smartterrarium 作成したもの できること 温室内の現在温度・湿度の取得 日中と夜間のパネルヒーターの稼働温度設定 4日分の温度・湿度遷移記録(15分おき)の確認 動作環境 Raspberry Pi Android スマートフォン 材料 Raspberry Pi (4 Model Bを推奨)) 温湿度センサーDHT22 スマートプラグHS105(p105でも可) パネルヒーター お手持ちの スマホ (今回は Android 機を使用) FastAPI Flutter 手順 Raspberry Pi に温湿度センサーDHT22を接続 スマートプラグHS105をLAN内に接続 パネルヒーターの電源プラグをスマートプラグHS105に接続 温湿度センサーとパネルヒーターを温室内に設置 温度によるパネルヒーターの制御プログラムを作成 温度・湿度記録用のデータベースと設定値保持用のデータベースをSQLite3で作成 制御プログラムをFastAPIを利用して、 Raspberry Pi 上でweb API 化 Flutterで操作画面を作成し、パネルヒーター制御のための API を叩く Flutterで作ったアプリを Android にUSB デバッグ でインストール 詳細 Raspberry Pi に温湿度センサーを接続 一番、つまづいたポイントはここかもしれません。GPIOピンに温湿度センサーを接続します。使用したDHT22はVCC 、DAT、GNDと3つの端子が存在しますが、VCCは3.3Vピン、DATがGPIOピン、GNDはGNDピンに接続する必要があります。 おすすめは配線しやすいので、VCCは1番ピン、DATは37番ピン(GPIO26)、GNDは29番ピンに接続することです。これらはすべて端に配置されているピンなので間違えずに配線できると思います。 ピンの配置がわからなくなったら、 Raspberry Pi のターミナルで $ pinout と実行するとこのようにピンの配置が表示されます 3V3 (1) (2) 5V GPIO2 (3) (4) 5V GPIO3 (5) (6) GND GPIO4 (7) (8) GPIO14 GND (9) (10) GPIO15 GPIO17 (11) (12) GPIO18 GPIO27 (13) (14) GND GPIO22 (15) (16) GPIO23 3V3 (17) (18) GPIO24 GPIO10 (19) (20) GND GPIO9 (21) (22) GPIO25 GPIO11 (23) (24) GPIO8 GND (25) (26) GPIO7 GPIO0 (27) (28) GPIO1 GPIO5 (29) (30) GND GPIO6 (31) (32) GPIO12 GPIO13 (33) (34) GND GPIO19 (35) (36) GPIO16 GPIO26 (37) (38) GPIO20 GND (39) (40) GPIO21 つまづいた理由は手元の Raspberry Pi 4がDHT22の出力を認識できなかったことでした。GPIOの設定は一通り確認したのですが認識せず、最初はDHT22の故障を疑っていたので arduino で動作確認したらすんなり動作しました。 しょうがないので、学生時代に買った Raspberry Pi 3 B+を引っ張り出してきて、動作確認することになりました。今度はDHT22から温度と湿度を取得できたものの、RAMが1GBの環境は快適にプログラミングできる環境でないので、その後かなりコーディングに苦戦しました。 まずは、必要なパッケージをインストールします。 $ sudo apt-get update $ sudo apt-get install build-essential python-dev さらに、DHT22の出力を認識するためのライブラリをインストールします(適当なフォルダ内で実行)。 $ sudo git clone https://github.com/adafruit/Adafruit_Python_DHT.git $ cd Adafruit_Python_DHT $ sudo python setup.py install DHT22読み取り用のプログラムを実装します。 import Adafruit_DHT as DHT import RPi.GPIO as GPIO # センサータイプの設定 SENSOR_TYPE = DHT.DHT22 # 使用するGPIOのピン番号 DHT_GPIO = 26 def read_dht(): try: humidity, temperature = DHT.read_retry( SENSOR_TYPE, DHT_GPIO) message_temp = "Temp= {0:0.1f} deg C".format(temperature) message_humidity = "Humidity= {0:0.1f} %".format(humidity) message = message_temp + ". " + message_humidity print(message) except: # センサから温度が取得できなかった際に、Nullが返るとエラーが起こるので応急処置 humidity = 0.0 temperature = 0.0 return temperature, humidity スマートプラグをLAN内に接続 こちらは公式のアプリを操作してください HS105初期設定 p105初期設定 Raspberry Pi が接続されているLANに接続できればOKです パネルヒーターの電源プラグをスマートプラグHS105に接続 パネルヒーターの電源プラグをスマートプラグに接続してください。 「 Raspberry Pi があるんだったら、リレー回路を組んで Raspberry Pi のIOから直接制御すればいいのでは?」と突っ込まれそうですが、リレー素子を購入したものの家庭用電源を自分が組んだ電気回路で制御するのは恐ろしくできませんでした。 スマートプラグは2000円程度で購入できるので、回路を組む手間を考えると素直にスマートプラグを使用したほうがいいと思います。 温湿度センサーとパネルヒーターを温室内に設置 お好きな様に設置してください。 温湿度センサーと Raspberry Pi を配線するので、 Raspberry Pi を温室の近くに設置する必要があり、温室から漏れた水で Raspberry Pi が故障しないか若干心配です。 どうせ近く設置するなら、 Raspberry Pi からの排熱も温室の加温に利用したいですね・・・。 温度によるパネルヒーターの制御プログラムを書く 今回は日中(7:00~20:00)と夜間(20:00~7:00)で設定温度を変えます。市販の サーモス タッドではなく、わざわざ Raspberry Pi でパネルヒーターを制御している理由はここにあります。ある程度夜間に温度を下げたいので、時間帯によってパネルヒーターの稼働温度を変えています( サーモス タッドも高いやつは時間帯によって設定温度を変えられますが・・・)。 というのも、温室だからといって24時間365日常夏にしたいわけじゃなくて、ある程度一日の寒暖差があった上で、冬は冬らしく枯れない程度に寒さを感じさせる必要があります。なので、この設定温度も寒さが厳しくなる1月や2月にはさらに下げようと考えています。冬に花が咲く品種もありますし、春に向けてちゃんと今が冬であることを理解できるように日中と夜間に温度差が生じるように設定します。 こちらはプラグの制御に用いたクラスの抜粋です。完成版は以下の レポジトリ で公開しています。 import Adafruit_DHT as DHT from tp_plug import * import sqlite3 # 温度センサーの出力を受けてスマートプラグを制御するためのクラス class ControlPlug: def __init__(self, threshold_day_temperature, threshold_night_temperature, over_temperature): # パネルヒーターのオンオフを管理するフラグ(0:off、1:on) self.heat_flag = 0 # 日中のパネルヒーターの稼働温度 self.threshold_day_temperature = threshold_day_temperature # 夜間のパネルヒーターの稼働温度 self.threshold_night_temperature = threshold_night_temperature # 超過温度(一度パネルヒーターが稼働すると稼働温度+超過温度を超えるまで稼働し続ける) self.over_temperature = over_temperature # 日中、夜間の切り替え温度 self.day_start_hour = 7 self.day_end_hour = 20 # 起動時に一度IOTプラグの電源を落とす print(TPLink_Plug("192.168.11.23").off()) # スマートプラグ(HS105)のスイッチング def switch_plug(self, temperature, dt_now): print("dt_now.hour:", dt_now.hour) # 夜間、ヒーターオフ時、温度低下でヒーターオン if (dt_now.hour < 7 or 19 < dt_now.hour) and temperature < self.threshold_night_temperature and self.heat_flag == 0: self.heat_flag = 1 print("night_on") print(TPLink_Plug("192.168.11.23").on()) # 昼間、ヒーターオフ時、温度低下でヒーターオン if (7 < dt_now.hour < 19) and temperature < self.threshold_day_temperature and self.heat_flag == 0: self.heat_flag = 1 print("day_on") print(TPLink_Plug("192.168.11.23").on()) # 夜間、ヒーターオン時、温度上昇でヒーターオフ if (dt_now.hour < 7 or 19 < dt_now.hour) and temperature > (self.threshold_night_temperature + self.over_temperature) and self.heat_flag == 1: self.heat_flag = 0 print("night_off") print(TPLink_Plug("192.168.11.23").off()) # 昼間、ヒーターオン時、温度上昇でヒーターオフ if (7 < dt_now.hour < 19) and temperature > (self.threshold_day_temperature + self.over_temperature) and self.heat_flag == 1: self.heat_flag = 0 print("day_off") print(TPLink_Plug("192.168.11.23").off()) return self.heat_flag over_temperature は温度が稼働温度を下回って、パネルヒーターがonになった後に温度が稼働温度を超えても加温し続けるための超過温度の設定値です。 あくまで稼働温度はこれ以上温度が下がると困るという値なので、パネルヒーターにより、温度が稼働温度を超えても余分に加温し続けるように設定します(パネルヒーターがoffになったあとすぐに温室内の温度が設定値を下回るのを防ぐためです)。 パネルヒーターが稼働しているかの判断はスマートプラグより情報を取得するのが難しかったので、 heat_flag という変数でパネルヒーターの稼働状況を保持しています。 ということで、スイッチングのif文の条件式がやや複雑になってます。 スマートプラグの制御モジュール(tp_plug.py)は、こちらのページの コードー を使用させていただきました。 コスパ最強IoT家電!TPLink製品をRaspberryPiから操作 温度・湿度記録用のデータベースと設定値保持用のデータベースをSQLite3で作成 半分終了しました。疲れていませんか?自分は9月の中旬に材料を買ったのですが、ここまでたどり着いたときには11月になっていました。 アドベントカレンダー にこのネタを投稿することが決まったので、少しペースを上げましたが、それでも今ギリギリになって記事を書いています。 このアプリの目玉機能にこれまでの温度・湿度記録を確認できるというものがあります。パネルヒーターがちゃんと動作しているのか確認するという意味合いもありますが、温室内の植物がある日突然調子を崩した際に原因を推測する手がかりになるので、個人的には必ず盛り込みたい機能でした。 今回は物理的な加湿機構を温室内に作成するのが間に合わなかったので湿度の調整はできませんが、温室内の植物は70%~に湿度を保つ必要があり、一瞬でも下回るとそれだけでダメージを負ってしまうので24時間の監視が重要になります。 データを保持するテーブルはシンプルなものにしました( カラム名 の 命名 がイケてない気がしますが気にしないことにします)。 温度・湿度記録テーブル(environmental_record) カラム名 内容 データ型 制約 measurement_time 計測日時 TEXT PRIMARY KEY temperature 計測温度 float NOT NULL humidity 計測湿度 float NOT NULL heat_flag パネルヒーター稼働フラグ int NOT NULL 設定値テーブル(parameter_set) カラム名 内容 データ型 制約 update_at 更新日時 TEXT PRIMARY KEY threshold_day_temperature 日中稼働温度 float NOT NULL threshold_night_temperature 夜間稼働温度 float NOT NULL diff_temperature 超過温度 float NOT NULL 作業的にはまず、 Raspberry Pi にSQLite3をインストールするところから始めます。 $ sudo apt install sqlite3 -y 次に、pipでSQLite3のモジュールをインストール。 $ pip install pysqlite3 次に タイムゾーン 設定のためのモジュールをインストール。 $ pip install pytz データベースの初期化用に以下のプログラムを作成しました。こちらのプログラムを一回実行すれば、同じ階層にSQLite3のデータベースが作成されます。 init_sql.py import sqlite3 import datetime import pytz dbname = 'smart-terrarium.db' # コネクタ作成。dbnameの名前を持つDBへ接続する。 conn = sqlite3.connect(dbname) cur = conn.cursor() # 温度・湿度記録用テーブルをリセットする # cur.execute('DROP TABLE IF EXISTS environmental_record') # テーブルの作成 cur.execute('CREATE TABLE environmental_record(measurement_time TEXT PRIMARY KEY,temperature float NOT NULL,humidity float NOT NULL,heat_flag int NOT NULL);') cur.execute('CREATE TABLE parameter_set(update_at TEXT PRIMARY KEY,threshold_day_temperature float NOT NULL,threshold_night_temperature float NOT NULL,diff_temperature float NOT NULL)') dt_now = datetime.datetime.now(pytz.timezone('Asia/Tokyo')) measurement_time = dt_now.strftime('%Y-%m-%d %H:%M:%S') parameter_data = [(measurement_time, 25.0, 22.0, 3.0)] cur.executemany("insert into parameter_set values (?, ?, ?, ?)", parameter_data) # 処理をコミット conn.commit() conn.close() これで、データベースの準備が整いました。 ここから、データの読み書き用のクラスを作成します。 実装するのは設定値の読み出し、更新。温度・湿度記録の読み出し(1レコード、複数レコード)、追加のためのプログラムです。 全部ここに書くと長いので、一部抜粋して紹介します。 utility.py # データの読み書き用のクラス class ReadWriteDB(): def __init__(self): self.dbname = 'smart-terrarium.db' # parameter_set読み出し def read_parameter_set(self): conn = sqlite3.connect(self.dbname) cur = conn.cursor() data = cur.execute( 'select * from parameter_set order by update_at desc limit 1') latest_record = data.fetchall() threshold_day_temperature = latest_record[0][1] threshold_night_temperature = latest_record[0][2] over_temperature = latest_record[0][3] conn.commit() conn.close() return threshold_day_temperature, threshold_night_temperature, over_temperature # 温度・湿度記録追加 def update_temperature_humidity(self, measurement_time, temperature, humidity, heat_flag): conn = sqlite3.connect(self.dbname) cur = conn.cursor() environmental_data = [ (measurement_time, temperature, humidity, heat_flag)] cur.executemany( "insert into environmental_record values (?, ?, ?, ?)", environmental_data).fetchall() conn.commit() conn.close() # 1 レコード取得 def read_one_records(self): # コネクタ作成 conn = sqlite3.connect(self.dbname) cur = conn.cursor() # 値の取得 data = cur.execute( 'select * from environmental_record order by measurement_time desc limit 1') records = data.fetchall() print(records[0][1]) return_value = { 'timestamp': records[0][0], 'temperature': records[0][1], 'humidity': records[0][2], 'heat_flag': records[0][3] } return return_value 制御プログラムをFastAPIを利用して、 Raspberry Pi 上でwebAPI化 いよいよ実装も佳境に入ってきました。 これまでに実装した Python 側の制御プログラムをwebAPIとして実装するためのプログラムをFastAPIで作成します。 FastAPIは Python でRESTful API を構築するためのマイクロ フレームワーク です。 FastAPIを使用するのは今回が初めてなのですが、RAMが1GBしかない Raspberry Pi 3 B+で実装しながら動かすので、 とにかく軽いこと、そして日本語のドキュメントが存在していること、最近 SNS 界隈で噂になっていることがFastAPIの選定理由でした。 実際利用してみると、 Raspberry Pi 3 B+でも快適に動かすことができました。これが、同時にリク エス トが飛んできた場合どうなるかわかりませんが、今回はLAN内で自分しか使用しないアプリを作るので問題ないです。 これまでに同じ Raspberry Pi で Django とSpringで作ったwebアプリを動かしたことがありました。普通に起動はしてくれるものの、サーバーが立ち上がっている際に、同時にエディターでコードの編集をしているともっさりしてまともにコーディングできませんでしたが、FastAPIはwebサーバーが起動していることを感じさせない使用感でした。 軽さよりも気に入ったのは、実装の簡単さですね。デ シリアライズ もルーティングも直感的に実装できるのが良かったです。きっと何十個もエンドポイントを用意したり、複雑な JSON をやり取りするには物足りないこともあるのかもしれませんが、日曜プログラミングにはぴったりな フレームワーク だと思います。 FastAPI公式ドキュメント まずはFastAPIと必要なモジュールをインストールします pip install fastapi pip install fastapi-utils uvicorn もインストール。 pip install "uvicorn[standard]" 実装は例の リポジトリ にまとめてあります。 ここからはFastAPIの気に入ったところだけまとめます。 smartterrarium.py # FastAPI app = FastAPI() @app.get("/") async def root(): return {"message": "This is the smart terrarium"} webサーバを立ち上げるのに必要な記述はこれだけ。あとは、 uvicorn smart-terrarium:app --reload とコマンドを実行するだけなのがいいですね。 フルスタックフレームワーク では、こうは行きませんよね。 15分おきに温度と湿度を出力する部分は以下のように実装しています。 from fastapi_utils.tasks import repeat_every # 定期実行用の関数 @app.get("/rest/") @app.on_event("startup") @repeat_every(seconds=60*15) def heater_control(): # 温度・湿度取得 temperature, humidity = control_plug.read_dht() リク エス トボディーのデ シリアライズ 。 # parameter_setのリクエストボディー class ParameterSet(BaseModel): day_temperature: int night_temperature: int diff_temperature: int # 設定値テーブル更新 @app.post("/update_parameter_set/") async def update_parameter_set(parameter_set: ParameterSet): dt_now = datetime.datetime.now(pytz.timezone('Asia/Tokyo')) measurement_time = dt_now.strftime('%Y-%m-%d %H:%M:%S.%f') read_write_db.update_parameter_set( measurement_time, int(parameter_set.day_temperature), int(parameter_set.night_temperature), int(parameter_set.diff_temperature)) return {"message": "ok", "day_temperature": parameter_set.day_temperature, "night_temperature": parameter_set.night_temperature, " diff_temperature": parameter_set.diff_temperature} ここらへんは、マイクロ フレームワーク としては標準的な機能だと思いますが(他のマイクロ フレームワーク を触ったことがないのでわからない)、AI系のプロジェクトにはかなり相性がいいなと感じました。 機械学習 モデルを実運用するとなると、だいたいPoCでモデルを作ってからあとで、webAPI化という話になりますが、あとからデータの入り口と出口としてさくっと API を実装できる感覚がすごく気に入りました。 あと、自動ドキュメント生成や今回は使用しませんでしたがデータベースとの接続もサポートされているのもいいですね。 自分がデータサイエンティストとして、日ごろ関わっているプロジェクトでも最近PoCを終えて実運用に入ろうとしているプロジェクトがありますが、その際に必要なのは既存のシステムに組み込まれて、モデルやデータ処理 スクリプト を運用するだけの API なのでFastAPIで十分です。 今回は初歩的な機能しか使用しませんでしたが、もうちょっと重厚なアプリケーションを実装すればその恩恵をもっと感じられるのではないでしょうか? Flutterで操作画面を作成し、パネルヒーター制御 API を叩く ここまで読んでいただいた方は、すでにお腹いっぱいだと思いますが、書いてる僕はもうヘトヘトです。ただ、最後に一番重いやつが残ってるんですよね。何から説明すればいいんでしょか? 何をしたかというと、 Android Studio でFlutterを使ってモバイルアプリを実装しました。画面は3つの本当にシンプルなアプリです。 モバイルアプリにした理由は Raspberry Pi でフロントエンドのwebサーバーを立ち上げたくなかったからです。気軽に温室の管理ができるのが今回のアプリのコンセプトなので、 スマホ から使う ユースケース を想定していたのですが、描画処理をRAMが1 MBの Raspberry Pi で行うよりは、豊富な計算リソースを活用できる スマートフォン を利用したほうがいいと判断しました。 Flutterを使った理由はいままで使ったことがなかったからです。React Nativeは以前 チュートリアル レベルのアプリを実装したことがあったのですが、Flutterは全く触ったことがありませんでした。ということで、ちょうどいいので触ってみようと思ったわけです。 Flutterのアプリのコードは こちら にあります。 現在温度表示画面 シンプルな現在温度と湿度の表示画面です。 こだわったところはとにかくUIの可愛さです。自分しか使わないアプリなので自分が可愛いと感じるデザインにしました。 ここの実装は特に困った部分はなかったです。Flutterのデフォルトのプロジェクトをいじるだけで実装できると思います。 パネルヒーター稼働温度設定画面 左右のプラス、マイナスボタンでパネルヒーターが稼働する設定温度を変更できます。 この画面が一番苦労した画面かもしれません。 プラスorマイナスボタンが押される ↓ Raspberry Pi 上の API が叩かれて、設定温度が変更される ↓ 現在の設定温度を Raspberry Pi 上の API から取得 ↓ 再描画 という順番で処理が進むのですが、再描画の際に現在の設定温度が取得されずにエラーを吐くという事態が頻発して、自分は全然非同期処理についてわかってないなと痛感しました。 温度遷移履歴確認画面 これまでの温度と湿度の記録をスクロールして確認できます。 ここが一番ざんねんな画面です。本当は取得期間を設定したり、折れ線グラフを描画したりしたかったんですが、力尽きました。ここについては年末にでも時間があったら実装します。 Flutterで作ったアプリを Android にUSB デバッグ でインストール 最後のステップです。 作ったアプリを手元の スマホ にインストールします。 あくまで個人利用が目的なので、USB デバッグ でインストールするという一番お手軽な方法を取りました。 やり方はかんたんで、 Android スマホ を開発者モードにしてからUSB デバッグ を有効にします。 そしたら、アプリを開発している スマホ に接続して Android Studio の実行先として スマホ を選ぶだけです。 インストールには10分程度時間がかかりました。 最後に 今回作ったアプリですが、個人的にものすごく重宝しています。 自分が前々から欲しかったアプリを形にしたので当たり前ですが、毎日使用してますね。やっぱり気軽に触れるUIがあることはアプリケーションの利便性を格段に向上させますし、何より頻繁に使ってもらえるアプリになりますね。 ただ追加したい機能はもっとあって、まずは加湿や送風の制御がしたいです。さらに、 Raspberry Pi をインターネット側からアクセス可能にして、カメラで中の様子を視覚的に確認できるようにすれば、旅行先から温室の様子を確認することが可能になります。 あと個人向けには需要がないと思いますが、農業まで視野を広げれば、画像認識AIを導入して、調子を崩した株の早期発見もできると思いますし、温室から取得できるデータを活用して収穫物の品質を向上させるなどの応用ができます。 このアプリを実装していて、就活時に農業向けのITプロジェクトを実施している会社に就職したいと考えていたことを思い出しました。当時は農業案件を実施している企業が少なくて、諦めてしまいましたが、いつかISIDで農業系のプロジェクトに挑戦したいですね。 ということで、この件についてお話がある社内外の方は、ぜひ徳原にお話しください。お役に立てることがあるかもしれません。 この長々とした記事をここまで読んでくださった方、本当にありがとうございます。植物×ITのネタはまたいつか投稿したいと思うので、その際も読んでいただければと思います(AITCwebサイトではAIやデータサイエンスに関して コラム を書いています)。 また、AITCでは新卒、中途問わず一緒に働いてくださる方を絶賛募集中です。 こちらの 採用情報ページ からカジュアル面談の申込みを受け付けています。 それでは。 参考文献 温度/湿度センサーDHT22をRaspberry Piで使用する方法 コスパ最強IoT家電!TPLink製品をRaspberryPiから操作 FastAPI公式ドキュメント 執筆: @tokuhara.hikaru 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
こんにちは。 XI本部 AIトランスフォーメーションセンター の徳原光です。ISID2022年 アドベントカレンダー の12月19日の記事を投稿します。 皆さん、仕事のいやしってありますか? お仕事にはストレスはつきもの。仕事の合間にメンタルをコン トロール する自分なりの方法があるって大事なことだと思うんですよね。 自分はこれです。 小さな温室に入った小さなジャングル。日々刻々と変化する熱帯植物の様子を眺めると、今頭の中でグルグル回っている悩みなんてどうでもいいなって気持ちになります。 温室を運用する上で一番大切なことは、当たり前ですが温度を保つことです(あと、湿度も大事)。なので、温室内の温度変化に注意しつつ、季節や天候によって設定温度を微妙に変更したくなるんですよね。 すでに、 Raspberry Pi に温湿度センサーDHT22を接続して温度遷移の記録を取りつつ、スマートプラグを利用して温室に設置したパネルヒーターの制御を行っていたのですが、パネルヒーターの稼働温度の変更や、温度遷移を確認するのにわざわざRsapberry Piに接続するのは面倒でした。 ということで個人で使用するために、 スマホ でかんたんに温度の監視やパネルヒーターの設定温度を変更するモバイルアプリを作成したので、そのレシピを紹介したいと思います。 同様のことに挑戦される方のご参考になればと思います。今回実装したコードはすべてこちらの リポジトリ に上げたので活用してください。 Pickerdot/smartterrarium 作成したもの できること 温室内の現在温度・湿度の取得 日中と夜間のパネルヒーターの稼働温度設定 4日分の温度・湿度遷移記録(15分おき)の確認 動作環境 Raspberry Pi Android スマートフォン 材料 Raspberry Pi (4 Model Bを推奨)) 温湿度センサーDHT22 スマートプラグHS105(p105でも可) パネルヒーター お手持ちの スマホ (今回は Android 機を使用) FastAPI Flutter 手順 Raspberry Pi に温湿度センサーDHT22を接続 スマートプラグHS105をLAN内に接続 パネルヒーターの電源プラグをスマートプラグHS105に接続 温湿度センサーとパネルヒーターを温室内に設置 温度によるパネルヒーターの制御プログラムを作成 温度・湿度記録用のデータベースと設定値保持用のデータベースをSQLite3で作成 制御プログラムをFastAPIを利用して、 Raspberry Pi 上でweb API 化 Flutterで操作画面を作成し、パネルヒーター制御のための API を叩く Flutterで作ったアプリを Android にUSB デバッグ でインストール 詳細 Raspberry Pi に温湿度センサーを接続 一番、つまづいたポイントはここかもしれません。GPIOピンに温湿度センサーを接続します。使用したDHT22はVCC 、DAT、GNDと3つの端子が存在しますが、VCCは3.3Vピン、DATがGPIOピン、GNDはGNDピンに接続する必要があります。 おすすめは配線しやすいので、VCCは1番ピン、DATは37番ピン(GPIO26)、GNDは29番ピンに接続することです。これらはすべて端に配置されているピンなので間違えずに配線できると思います。 ピンの配置がわからなくなったら、 Raspberry Pi のターミナルで $ pinout と実行するとこのようにピンの配置が表示されます 3V3 (1) (2) 5V GPIO2 (3) (4) 5V GPIO3 (5) (6) GND GPIO4 (7) (8) GPIO14 GND (9) (10) GPIO15 GPIO17 (11) (12) GPIO18 GPIO27 (13) (14) GND GPIO22 (15) (16) GPIO23 3V3 (17) (18) GPIO24 GPIO10 (19) (20) GND GPIO9 (21) (22) GPIO25 GPIO11 (23) (24) GPIO8 GND (25) (26) GPIO7 GPIO0 (27) (28) GPIO1 GPIO5 (29) (30) GND GPIO6 (31) (32) GPIO12 GPIO13 (33) (34) GND GPIO19 (35) (36) GPIO16 GPIO26 (37) (38) GPIO20 GND (39) (40) GPIO21 つまづいた理由は手元の Raspberry Pi 4がDHT22の出力を認識できなかったことでした。GPIOの設定は一通り確認したのですが認識せず、最初はDHT22の故障を疑っていたので arduino で動作確認したらすんなり動作しました。 しょうがないので、学生時代に買った Raspberry Pi 3 B+を引っ張り出してきて、動作確認することになりました。今度はDHT22から温度と湿度を取得できたものの、RAMが1GBの環境は快適にプログラミングできる環境でないので、その後かなりコーディングに苦戦しました。 まずは、必要なパッケージをインストールします。 $ sudo apt-get update $ sudo apt-get install build-essential python-dev さらに、DHT22の出力を認識するためのライブラリをインストールします(適当なフォルダ内で実行)。 $ sudo git clone https://github.com/adafruit/Adafruit_Python_DHT.git $ cd Adafruit_Python_DHT $ sudo python setup.py install DHT22読み取り用のプログラムを実装します。 import Adafruit_DHT as DHT import RPi.GPIO as GPIO # センサータイプの設定 SENSOR_TYPE = DHT.DHT22 # 使用するGPIOのピン番号 DHT_GPIO = 26 def read_dht(): try: humidity, temperature = DHT.read_retry( SENSOR_TYPE, DHT_GPIO) message_temp = "Temp= {0:0.1f} deg C".format(temperature) message_humidity = "Humidity= {0:0.1f} %".format(humidity) message = message_temp + ". " + message_humidity print(message) except: # センサから温度が取得できなかった際に、Nullが返るとエラーが起こるので応急処置 humidity = 0.0 temperature = 0.0 return temperature, humidity スマートプラグをLAN内に接続 こちらは公式のアプリを操作してください HS105初期設定 p105初期設定 Raspberry Pi が接続されているLANに接続できればOKです パネルヒーターの電源プラグをスマートプラグHS105に接続 パネルヒーターの電源プラグをスマートプラグに接続してください。 「 Raspberry Pi があるんだったら、リレー回路を組んで Raspberry Pi のIOから直接制御すればいいのでは?」と突っ込まれそうですが、リレー素子を購入したものの家庭用電源を自分が組んだ電気回路で制御するのは恐ろしくできませんでした。 スマートプラグは2000円程度で購入できるので、回路を組む手間を考えると素直にスマートプラグを使用したほうがいいと思います。 温湿度センサーとパネルヒーターを温室内に設置 お好きな様に設置してください。 温湿度センサーと Raspberry Pi を配線するので、 Raspberry Pi を温室の近くに設置する必要があり、温室から漏れた水で Raspberry Pi が故障しないか若干心配です。 どうせ近く設置するなら、 Raspberry Pi からの排熱も温室の加温に利用したいですね・・・。 温度によるパネルヒーターの制御プログラムを書く 今回は日中(7:00~20:00)と夜間(20:00~7:00)で設定温度を変えます。市販の サーモス タッドではなく、わざわざ Raspberry Pi でパネルヒーターを制御している理由はここにあります。ある程度夜間に温度を下げたいので、時間帯によってパネルヒーターの稼働温度を変えています( サーモス タッドも高いやつは時間帯によって設定温度を変えられますが・・・)。 というのも、温室だからといって24時間365日常夏にしたいわけじゃなくて、ある程度一日の寒暖差があった上で、冬は冬らしく枯れない程度に寒さを感じさせる必要があります。なので、この設定温度も寒さが厳しくなる1月や2月にはさらに下げようと考えています。冬に花が咲く品種もありますし、春に向けてちゃんと今が冬であることを理解できるように日中と夜間に温度差が生じるように設定します。 こちらはプラグの制御に用いたクラスの抜粋です。完成版は以下の レポジトリ で公開しています。 import Adafruit_DHT as DHT from tp_plug import * import sqlite3 # 温度センサーの出力を受けてスマートプラグを制御するためのクラス class ControlPlug: def __init__(self, threshold_day_temperature, threshold_night_temperature, over_temperature): # パネルヒーターのオンオフを管理するフラグ(0:off、1:on) self.heat_flag = 0 # 日中のパネルヒーターの稼働温度 self.threshold_day_temperature = threshold_day_temperature # 夜間のパネルヒーターの稼働温度 self.threshold_night_temperature = threshold_night_temperature # 超過温度(一度パネルヒーターが稼働すると稼働温度+超過温度を超えるまで稼働し続ける) self.over_temperature = over_temperature # 日中、夜間の切り替え温度 self.day_start_hour = 7 self.day_end_hour = 20 # 起動時に一度IOTプラグの電源を落とす print(TPLink_Plug("192.168.11.23").off()) # スマートプラグ(HS105)のスイッチング def switch_plug(self, temperature, dt_now): print("dt_now.hour:", dt_now.hour) # 夜間、ヒーターオフ時、温度低下でヒーターオン if (dt_now.hour < 7 or 19 < dt_now.hour) and temperature < self.threshold_night_temperature and self.heat_flag == 0: self.heat_flag = 1 print("night_on") print(TPLink_Plug("192.168.11.23").on()) # 昼間、ヒーターオフ時、温度低下でヒーターオン if (7 < dt_now.hour < 19) and temperature < self.threshold_day_temperature and self.heat_flag == 0: self.heat_flag = 1 print("day_on") print(TPLink_Plug("192.168.11.23").on()) # 夜間、ヒーターオン時、温度上昇でヒーターオフ if (dt_now.hour < 7 or 19 < dt_now.hour) and temperature > (self.threshold_night_temperature + self.over_temperature) and self.heat_flag == 1: self.heat_flag = 0 print("night_off") print(TPLink_Plug("192.168.11.23").off()) # 昼間、ヒーターオン時、温度上昇でヒーターオフ if (7 < dt_now.hour < 19) and temperature > (self.threshold_day_temperature + self.over_temperature) and self.heat_flag == 1: self.heat_flag = 0 print("day_off") print(TPLink_Plug("192.168.11.23").off()) return self.heat_flag over_temperature は温度が稼働温度を下回って、パネルヒーターがonになった後に温度が稼働温度を超えても加温し続けるための超過温度の設定値です。 あくまで稼働温度はこれ以上温度が下がると困るという値なので、パネルヒーターにより、温度が稼働温度を超えても余分に加温し続けるように設定します(パネルヒーターがoffになったあとすぐに温室内の温度が設定値を下回るのを防ぐためです)。 パネルヒーターが稼働しているかの判断はスマートプラグより情報を取得するのが難しかったので、 heat_flag という変数でパネルヒーターの稼働状況を保持しています。 ということで、スイッチングのif文の条件式がやや複雑になってます。 スマートプラグの制御モジュール(tp_plug.py)は、こちらのページの コードー を使用させていただきました。 コスパ最強IoT家電!TPLink製品をRaspberryPiから操作 温度・湿度記録用のデータベースと設定値保持用のデータベースをSQLite3で作成 半分終了しました。疲れていませんか?自分は9月の中旬に材料を買ったのですが、ここまでたどり着いたときには11月になっていました。 アドベントカレンダー にこのネタを投稿することが決まったので、少しペースを上げましたが、それでも今ギリギリになって記事を書いています。 このアプリの目玉機能にこれまでの温度・湿度記録を確認できるというものがあります。パネルヒーターがちゃんと動作しているのか確認するという意味合いもありますが、温室内の植物がある日突然調子を崩した際に原因を推測する手がかりになるので、個人的には必ず盛り込みたい機能でした。 今回は物理的な加湿機構を温室内に作成するのが間に合わなかったので湿度の調整はできませんが、温室内の植物は70%~に湿度を保つ必要があり、一瞬でも下回るとそれだけでダメージを負ってしまうので24時間の監視が重要になります。 データを保持するテーブルはシンプルなものにしました( カラム名 の 命名 がイケてない気がしますが気にしないことにします)。 温度・湿度記録テーブル(environmental_record) カラム名 内容 データ型 制約 measurement_time 計測日時 TEXT PRIMARY KEY temperature 計測温度 float NOT NULL humidity 計測湿度 float NOT NULL heat_flag パネルヒーター稼働フラグ int NOT NULL 設定値テーブル(parameter_set) カラム名 内容 データ型 制約 update_at 更新日時 TEXT PRIMARY KEY threshold_day_temperature 日中稼働温度 float NOT NULL threshold_night_temperature 夜間稼働温度 float NOT NULL diff_temperature 超過温度 float NOT NULL 作業的にはまず、 Raspberry Pi にSQLite3をインストールするところから始めます。 $ sudo apt install sqlite3 -y 次に、pipでSQLite3のモジュールをインストール。 $ pip install pysqlite3 次に タイムゾーン 設定のためのモジュールをインストール。 $ pip install pytz データベースの初期化用に以下のプログラムを作成しました。こちらのプログラムを一回実行すれば、同じ階層にSQLite3のデータベースが作成されます。 init_sql.py import sqlite3 import datetime import pytz dbname = 'smart-terrarium.db' # コネクタ作成。dbnameの名前を持つDBへ接続する。 conn = sqlite3.connect(dbname) cur = conn.cursor() # 温度・湿度記録用テーブルをリセットする # cur.execute('DROP TABLE IF EXISTS environmental_record') # テーブルの作成 cur.execute('CREATE TABLE environmental_record(measurement_time TEXT PRIMARY KEY,temperature float NOT NULL,humidity float NOT NULL,heat_flag int NOT NULL);') cur.execute('CREATE TABLE parameter_set(update_at TEXT PRIMARY KEY,threshold_day_temperature float NOT NULL,threshold_night_temperature float NOT NULL,diff_temperature float NOT NULL)') dt_now = datetime.datetime.now(pytz.timezone('Asia/Tokyo')) measurement_time = dt_now.strftime('%Y-%m-%d %H:%M:%S') parameter_data = [(measurement_time, 25.0, 22.0, 3.0)] cur.executemany("insert into parameter_set values (?, ?, ?, ?)", parameter_data) # 処理をコミット conn.commit() conn.close() これで、データベースの準備が整いました。 ここから、データの読み書き用のクラスを作成します。 実装するのは設定値の読み出し、更新。温度・湿度記録の読み出し(1レコード、複数レコード)、追加のためのプログラムです。 全部ここに書くと長いので、一部抜粋して紹介します。 utility.py # データの読み書き用のクラス class ReadWriteDB(): def __init__(self): self.dbname = 'smart-terrarium.db' # parameter_set読み出し def read_parameter_set(self): conn = sqlite3.connect(self.dbname) cur = conn.cursor() data = cur.execute( 'select * from parameter_set order by update_at desc limit 1') latest_record = data.fetchall() threshold_day_temperature = latest_record[0][1] threshold_night_temperature = latest_record[0][2] over_temperature = latest_record[0][3] conn.commit() conn.close() return threshold_day_temperature, threshold_night_temperature, over_temperature # 温度・湿度記録追加 def update_temperature_humidity(self, measurement_time, temperature, humidity, heat_flag): conn = sqlite3.connect(self.dbname) cur = conn.cursor() environmental_data = [ (measurement_time, temperature, humidity, heat_flag)] cur.executemany( "insert into environmental_record values (?, ?, ?, ?)", environmental_data).fetchall() conn.commit() conn.close() # 1 レコード取得 def read_one_records(self): # コネクタ作成 conn = sqlite3.connect(self.dbname) cur = conn.cursor() # 値の取得 data = cur.execute( 'select * from environmental_record order by measurement_time desc limit 1') records = data.fetchall() print(records[0][1]) return_value = { 'timestamp': records[0][0], 'temperature': records[0][1], 'humidity': records[0][2], 'heat_flag': records[0][3] } return return_value 制御プログラムをFastAPIを利用して、 Raspberry Pi 上でwebAPI化 いよいよ実装も佳境に入ってきました。 これまでに実装した Python 側の制御プログラムをwebAPIとして実装するためのプログラムをFastAPIで作成します。 FastAPIは Python でRESTful API を構築するためのマイクロ フレームワーク です。 FastAPIを使用するのは今回が初めてなのですが、RAMが1GBしかない Raspberry Pi 3 B+で実装しながら動かすので、 とにかく軽いこと、そして日本語のドキュメントが存在していること、最近 SNS 界隈で噂になっていることがFastAPIの選定理由でした。 実際利用してみると、 Raspberry Pi 3 B+でも快適に動かすことができました。これが、同時にリク エス トが飛んできた場合どうなるかわかりませんが、今回はLAN内で自分しか使用しないアプリを作るので問題ないです。 これまでに同じ Raspberry Pi で Django とSpringで作ったwebアプリを動かしたことがありました。普通に起動はしてくれるものの、サーバーが立ち上がっている際に、同時にエディターでコードの編集をしているともっさりしてまともにコーディングできませんでしたが、FastAPIはwebサーバーが起動していることを感じさせない使用感でした。 軽さよりも気に入ったのは、実装の簡単さですね。デ シリアライズ もルーティングも直感的に実装できるのが良かったです。きっと何十個もエンドポイントを用意したり、複雑な JSON をやり取りするには物足りないこともあるのかもしれませんが、日曜プログラミングにはぴったりな フレームワーク だと思います。 FastAPI公式ドキュメント まずはFastAPIと必要なモジュールをインストールします pip install fastapi pip install fastapi-utils uvicorn もインストール。 pip install "uvicorn[standard]" 実装は例の リポジトリ にまとめてあります。 ここからはFastAPIの気に入ったところだけまとめます。 smartterrarium.py # FastAPI app = FastAPI() @app.get("/") async def root(): return {"message": "This is the smart terrarium"} webサーバを立ち上げるのに必要な記述はこれだけ。あとは、 uvicorn smart-terrarium:app --reload とコマンドを実行するだけなのがいいですね。 フルスタックフレームワーク では、こうは行きませんよね。 15分おきに温度と湿度を出力する部分は以下のように実装しています。 from fastapi_utils.tasks import repeat_every # 定期実行用の関数 @app.get("/rest/") @app.on_event("startup") @repeat_every(seconds=60*15) def heater_control(): # 温度・湿度取得 temperature, humidity = control_plug.read_dht() リク エス トボディーのデ シリアライズ 。 # parameter_setのリクエストボディー class ParameterSet(BaseModel): day_temperature: int night_temperature: int diff_temperature: int # 設定値テーブル更新 @app.post("/update_parameter_set/") async def update_parameter_set(parameter_set: ParameterSet): dt_now = datetime.datetime.now(pytz.timezone('Asia/Tokyo')) measurement_time = dt_now.strftime('%Y-%m-%d %H:%M:%S.%f') read_write_db.update_parameter_set( measurement_time, int(parameter_set.day_temperature), int(parameter_set.night_temperature), int(parameter_set.diff_temperature)) return {"message": "ok", "day_temperature": parameter_set.day_temperature, "night_temperature": parameter_set.night_temperature, " diff_temperature": parameter_set.diff_temperature} ここらへんは、マイクロ フレームワーク としては標準的な機能だと思いますが(他のマイクロ フレームワーク を触ったことがないのでわからない)、AI系のプロジェクトにはかなり相性がいいなと感じました。 機械学習 モデルを実運用するとなると、だいたいPoCでモデルを作ってからあとで、webAPI化という話になりますが、あとからデータの入り口と出口としてさくっと API を実装できる感覚がすごく気に入りました。 あと、自動ドキュメント生成や今回は使用しませんでしたがデータベースとの接続もサポートされているのもいいですね。 自分がデータサイエンティストとして、日ごろ関わっているプロジェクトでも最近PoCを終えて実運用に入ろうとしているプロジェクトがありますが、その際に必要なのは既存のシステムに組み込まれて、モデルやデータ処理 スクリプト を運用するだけの API なのでFastAPIで十分です。 今回は初歩的な機能しか使用しませんでしたが、もうちょっと重厚なアプリケーションを実装すればその恩恵をもっと感じられるのではないでしょうか? Flutterで操作画面を作成し、パネルヒーター制御 API を叩く ここまで読んでいただいた方は、すでにお腹いっぱいだと思いますが、書いてる僕はもうヘトヘトです。ただ、最後に一番重いやつが残ってるんですよね。何から説明すればいいんでしょか? 何をしたかというと、 Android Studio でFlutterを使ってモバイルアプリを実装しました。画面は3つの本当にシンプルなアプリです。 モバイルアプリにした理由は Raspberry Pi でフロントエンドのwebサーバーを立ち上げたくなかったからです。気軽に温室の管理ができるのが今回のアプリのコンセプトなので、 スマホ から使う ユースケース を想定していたのですが、描画処理をRAMが1 MBの Raspberry Pi で行うよりは、豊富な計算リソースを活用できる スマートフォン を利用したほうがいいと判断しました。 Flutterを使った理由はいままで使ったことがなかったからです。React Nativeは以前 チュートリアル レベルのアプリを実装したことがあったのですが、Flutterは全く触ったことがありませんでした。ということで、ちょうどいいので触ってみようと思ったわけです。 Flutterのアプリのコードは こちら にあります。 現在温度表示画面 シンプルな現在温度と湿度の表示画面です。 こだわったところはとにかくUIの可愛さです。自分しか使わないアプリなので自分が可愛いと感じるデザインにしました。 ここの実装は特に困った部分はなかったです。Flutterのデフォルトのプロジェクトをいじるだけで実装できると思います。 パネルヒーター稼働温度設定画面 左右のプラス、マイナスボタンでパネルヒーターが稼働する設定温度を変更できます。 この画面が一番苦労した画面かもしれません。 プラスorマイナスボタンが押される ↓ Raspberry Pi 上の API が叩かれて、設定温度が変更される ↓ 現在の設定温度を Raspberry Pi 上の API から取得 ↓ 再描画 という順番で処理が進むのですが、再描画の際に現在の設定温度が取得されずにエラーを吐くという事態が頻発して、自分は全然非同期処理についてわかってないなと痛感しました。 温度遷移履歴確認画面 これまでの温度と湿度の記録をスクロールして確認できます。 ここが一番ざんねんな画面です。本当は取得期間を設定したり、折れ線グラフを描画したりしたかったんですが、力尽きました。ここについては年末にでも時間があったら実装します。 Flutterで作ったアプリを Android にUSB デバッグ でインストール 最後のステップです。 作ったアプリを手元の スマホ にインストールします。 あくまで個人利用が目的なので、USB デバッグ でインストールするという一番お手軽な方法を取りました。 やり方はかんたんで、 Android スマホ を開発者モードにしてからUSB デバッグ を有効にします。 そしたら、アプリを開発している スマホ に接続して Android Studio の実行先として スマホ を選ぶだけです。 インストールには10分程度時間がかかりました。 最後に 今回作ったアプリですが、個人的にものすごく重宝しています。 自分が前々から欲しかったアプリを形にしたので当たり前ですが、毎日使用してますね。やっぱり気軽に触れるUIがあることはアプリケーションの利便性を格段に向上させますし、何より頻繁に使ってもらえるアプリになりますね。 ただ追加したい機能はもっとあって、まずは加湿や送風の制御がしたいです。さらに、 Raspberry Pi をインターネット側からアクセス可能にして、カメラで中の様子を視覚的に確認できるようにすれば、旅行先から温室の様子を確認することが可能になります。 あと個人向けには需要がないと思いますが、農業まで視野を広げれば、画像認識AIを導入して、調子を崩した株の早期発見もできると思いますし、温室から取得できるデータを活用して収穫物の品質を向上させるなどの応用ができます。 このアプリを実装していて、就活時に農業向けのITプロジェクトを実施している会社に就職したいと考えていたことを思い出しました。当時は農業案件を実施している企業が少なくて、諦めてしまいましたが、いつかISIDで農業系のプロジェクトに挑戦したいですね。 ということで、この件についてお話がある社内外の方は、ぜひ徳原にお話しください。お役に立てることがあるかもしれません。 この長々とした記事をここまで読んでくださった方、本当にありがとうございます。植物×ITのネタはまたいつか投稿したいと思うので、その際も読んでいただければと思います(AITCwebサイトではAIやデータサイエンスに関して コラム を書いています)。 また、AITCでは新卒、中途問わず一緒に働いてくださる方を絶賛募集中です。 こちらの 採用情報ページ からカジュアル面談の申込みを受け付けています。 それでは。 参考文献 温度/湿度センサーDHT22をRaspberry Piで使用する方法 コスパ最強IoT家電!TPLink製品をRaspberryPiから操作 FastAPI公式ドキュメント 執筆: @tokuhara.hikaru 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました )
アバター
最近、朝目覚めた時の外の光がキラキラとしていて、空気が澄んでいるのを感じています。UXデザインセンターのふじさきです。 先日、このISIDのテックブログがリニューアルしました! テックブログを刷新するにあたり、デザイン担当としてお手伝いをしました。 デザインを変えるときは、色やフォントを思いつきのままに描いていくのではなく、意味や理由があって成り立つものだと考えています。 今回は、リニューアルにあたって押さえておくべきデザインの事前整理ポイントに注力しながら進めていきました。その一部始終をお伝えしたいと思います。 ことのはじまり ターゲットの解像度を上げる 現状の悩みごと/現状のテックブログをつくるときに考えたことを整理 テックブログで重視したいことを整理 競合調査とベンチマーク デザインを形にする 最後に ことのはじまり 「ISIDのテックブログが、特にシンプルすぎるので、ぜひご協力を……」 ある日、社内からこんな声かけがありました。 (当時のテックブログサンプル画面↓) 「よっしゃ、やってやろうじゃないの」と意気揚々と参戦した私は、まず事前情報をインプットするために状況整理からはじめました。 やったことは以下の4点。 ターゲットの整理 現状の悩みごとを整理 現状のテックブログをつくるときに考えたことを整理 テックブログで重視したいことを整理 これらをテックブログ事務局の4名と、miroを使いながら MTG 形式で対話していきました。 ターゲットの解像度を上げる ISIDテックブログの主なユーザーターゲットはこちらの3つです。 採用候補者 参照するISID社員 執筆するISID社員 ここからさらにターゲットたちの解像度を上げることで、ブログそのものの存在意義につながり、デザインの根拠にもひもづきます。 どんなテックブログであるべきなのか、以下の質問を投げかけながら 言語化 を進めていきました。 このターゲットは何を知りたくてテックブログを見るのか? テックブログを通して、ターゲットに何を持ち帰ってもらえたら嬉しいか? 執筆するISID社員が執筆したくなるモチベーションは何か? この整理の中で、事務局のみなさんの思いに触れることができたのは、私にとって大きな発見でした。 1つめの問いで、「 等身大のISID 」というキーワードがでてきました。ここから派生し、「ISIDのテックブログでは新しいことを書かなくてもいい、最新性でなく、仕事への熱量、やりがい、楽しさが伝わるといい」という話が展開されました。 私はてっきりテックブログ=最新技術を研究して、その中身を発信しているばかりだと思ってました(もちろんそういう記事もあります)が、それよりも 社員自身が楽しんでいる様子やオリジナルな部分を大事にしている 、という思いを知ることができたのです。 そのおかげで、この後に続くデザインの方針やコンテンツ提供のスタイルに関して、軸がぶれないように進めることができました。 現状の悩みごと/現状のテックブログをつくるときに考えたことを整理 次に、現状の悩みごとを洗い出していってもらいました。その中身は、「 アイキャッチ 画像」「ヘッダー画像」といった画像まわりのお悩みが目立ちました。 その後、現状のテックブログをつくるときに考えていたことを洗い出してみると・・・ スマホ でひまつぶしにちょっと読む はてなブログ の標準機能でつくる(運用や実装負荷の軽減) JavaScript をゴリゴリ書かない 画像の作りこみはしない などなど といったように、お悩みとつくるときの方針が表裏していたんです。 つまり、サクサクと記事の内容を読むことができる、記事内容に集中できるようにするために、凝った表現を極力削ぎ落とし、読み込み速度を最適にするためにつくられていました。 この方針はとても大事な観点です。画像やギミックで華やかに演出することもできますが、それで記事の内容が見づらかったり、読み込みに待たされるブログを、ターゲットユーザーは果たして望んでいるのでしょうか・・・? テックブログで重視したいことを整理 そんな問いかけについてみなさんで議論し、改めてターゲットユーザーについて考え、届けたいことを見直しながら、最終的には 「理解しやすい」「ISIDらしさ」 といった点を重視してテックブログを発信していこうという結論になりました。 これまでの話を踏まえて整理した、テックブログで重視したいこと 見つけやすくて理解しやすい 他の読者の反応がわかる いずれにも魅力的(継続的にアクセスしたい、かつ、ISIDらしさ・独自性がある)と思われたい 独自性・ISIDらしさ=自由さ、楽しんで仕事してる 視認性・検索性・カテゴライズ 見た目がモダンで親しみやすい などなど 具体的なやりざまとしては、以下のような方向性でリニューアルを進めていきました。 コンテンツの内容を極力わかりやすい形にレイアウトしなおす ISIDのコーポレートカラーであるグレーをつかいながらも、単調にならないデザインにする 画像による演出はするものの、表示速度や目にやさしい画面デザインにする 競合調査と ベンチマーク テックブログを新しくするにあたり、他社動向を把握しておくことも大事なプロセスのひとつです。巷のテックブログを マッピング し、我々のテックブログはどのあたりをターゲットとするか議論しました。 ここで主に焦点があたったのは情報のレイアウトについて。 ISIDのもともとのテックブログは記事が長めに掲載された状態で縦並びになっていました。 2列がいいのか?3列がいいのか?そもそも ウィジェット 類はどのくらい載せる?といった検討事項がたくさん飛び交いました。 ここでとった整理の方法は、 コンテンツの優先度を決める ことです。 テックブログ事務局のみなさんと議論を交わしながら、「ISIDのテックブログは 記事命 でいく」「その 記事がたくさんあることを表現 していく」という大方針になりました。 ここが決まれば、取捨選択は円滑に進みます。情報はなるべく多く見えるように3列表示にする、 ウィジェット はページの後半に載せる、と具体的に実施するレイアウト方針もすんなり決まっていきました。 デザインを形にする 方針が決まれば、あとは画面のデザインです。いままでの内容を考慮しデザインを複数案作成し、どの案がいいかを決定し、細かい部分を調整していきました。 そうしてできあがったのが現状のデザインです。(下のデザイン案だと左案が近しい形です) 今回のデザインで気をつけたことは、なるべく標準に合わせ開発の負担をへらす(ようにしたけど実際はどうだったかな)、ISIDらしい誠実さもありながら記事ひとつひとつを丁寧に扱うデザインにする、という2点です。 前段の4つの整理がなければ、この点にたどり着けませんでした。 みんなの意見をまとめ、整理して納得しながら進めていくことで、最終的にたどり着いた形も満足度の高いものに仕上げることができたと思います。 実装に関する具体的な内容については こちらの記事 でご紹介しています。 最後に 今回はデザインの整理ポイントを中心に、リニューアルの経緯をお話させていただきました。 ISIDのテックブログはとにかく中身で勝負!しております。きっと等身大のISIDを垣間見れると思いますので、是非ご一読くださいませ。 執筆: @g-uxdc 、レビュー: @higa ( Shodo で執筆されました )
アバター
最近、朝目覚めた時の外の光がキラキラとしていて、空気が澄んでいるのを感じています。UXデザインセンターのふじさきです。 先日、このISIDのテックブログがリニューアルしました! テックブログを刷新するにあたり、デザイン担当としてお手伝いをしました。 デザインを変えるときは、色やフォントを思いつきのままに描いていくのではなく、意味や理由があって成り立つものだと考えています。 今回は、リニューアルにあたって押さえておくべきデザインの事前整理ポイントに注力しながら進めていきました。その一部始終をお伝えしたいと思います。 ことのはじまり ターゲットの解像度を上げる 現状の悩みごと/現状のテックブログをつくるときに考えたことを整理 テックブログで重視したいことを整理 競合調査とベンチマーク デザインを形にする 最後に ことのはじまり 「ISIDのテックブログが、特にシンプルすぎるので、ぜひご協力を……」 ある日、社内からこんな声かけがありました。 (当時のテックブログサンプル画面↓) 「よっしゃ、やってやろうじゃないの」と意気揚々と参戦した私は、まず事前情報をインプットするために状況整理からはじめました。 やったことは以下の4点。 ターゲットの整理 現状の悩みごとを整理 現状のテックブログをつくるときに考えたことを整理 テックブログで重視したいことを整理 これらをテックブログ事務局の4名と、miroを使いながら MTG 形式で対話していきました。 ターゲットの解像度を上げる ISIDテックブログの主なユーザーターゲットはこちらの3つです。 採用候補者 参照するISID社員 執筆するISID社員 ここからさらにターゲットたちの解像度を上げることで、ブログそのものの存在意義につながり、デザインの根拠にもひもづきます。 どんなテックブログであるべきなのか、以下の質問を投げかけながら 言語化 を進めていきました。 このターゲットは何を知りたくてテックブログを見るのか? テックブログを通して、ターゲットに何を持ち帰ってもらえたら嬉しいか? 執筆するISID社員が執筆したくなるモチベーションは何か? この整理の中で、事務局のみなさんの思いに触れることができたのは、私にとって大きな発見でした。 1つめの問いで、「 等身大のISID 」というキーワードがでてきました。ここから派生し、「ISIDのテックブログでは新しいことを書かなくてもいい、最新性でなく、仕事への熱量、やりがい、楽しさが伝わるといい」という話が展開されました。 私はてっきりテックブログ=最新技術を研究して、その中身を発信しているばかりだと思ってました(もちろんそういう記事もあります)が、それよりも 社員自身が楽しんでいる様子やオリジナルな部分を大事にしている 、という思いを知ることができたのです。 そのおかげで、この後に続くデザインの方針やコンテンツ提供のスタイルに関して、軸がぶれないように進めることができました。 現状の悩みごと/現状のテックブログをつくるときに考えたことを整理 次に、現状の悩みごとを洗い出していってもらいました。その中身は、「 アイキャッチ 画像」「ヘッダー画像」といった画像まわりのお悩みが目立ちました。 その後、現状のテックブログをつくるときに考えていたことを洗い出してみると・・・ スマホ でひまつぶしにちょっと読む はてなブログ の標準機能でつくる(運用や実装負荷の軽減) JavaScript をゴリゴリ書かない 画像の作りこみはしない などなど といったように、お悩みとつくるときの方針が表裏していたんです。 つまり、サクサクと記事の内容を読むことができる、記事内容に集中できるようにするために、凝った表現を極力削ぎ落とし、読み込み速度を最適にするためにつくられていました。 この方針はとても大事な観点です。画像やギミックで華やかに演出することもできますが、それで記事の内容が見づらかったり、読み込みに待たされるブログを、ターゲットユーザーは果たして望んでいるのでしょうか・・・? テックブログで重視したいことを整理 そんな問いかけについてみなさんで議論し、改めてターゲットユーザーについて考え、届けたいことを見直しながら、最終的には 「理解しやすい」「ISIDらしさ」 といった点を重視してテックブログを発信していこうという結論になりました。 これまでの話を踏まえて整理した、テックブログで重視したいこと 見つけやすくて理解しやすい 他の読者の反応がわかる いずれにも魅力的(継続的にアクセスしたい、かつ、ISIDらしさ・独自性がある)と思われたい 独自性・ISIDらしさ=自由さ、楽しんで仕事してる 視認性・検索性・カテゴライズ 見た目がモダンで親しみやすい などなど 具体的なやりざまとしては、以下のような方向性でリニューアルを進めていきました。 コンテンツの内容を極力わかりやすい形にレイアウトしなおす ISIDのコーポレートカラーであるグレーをつかいながらも、単調にならないデザインにする 画像による演出はするものの、表示速度や目にやさしい画面デザインにする 競合調査と ベンチマーク テックブログを新しくするにあたり、他社動向を把握しておくことも大事なプロセスのひとつです。巷のテックブログを マッピング し、我々のテックブログはどのあたりをターゲットとするか議論しました。 ここで主に焦点があたったのは情報のレイアウトについて。 ISIDのもともとのテックブログは記事が長めに掲載された状態で縦並びになっていました。 2列がいいのか?3列がいいのか?そもそも ウィジェット 類はどのくらい載せる?といった検討事項がたくさん飛び交いました。 ここでとった整理の方法は、 コンテンツの優先度を決める ことです。 テックブログ事務局のみなさんと議論を交わしながら、「ISIDのテックブログは 記事命 でいく」「その 記事がたくさんあることを表現 していく」という大方針になりました。 ここが決まれば、取捨選択は円滑に進みます。情報はなるべく多く見えるように3列表示にする、 ウィジェット はページの後半に載せる、と具体的に実施するレイアウト方針もすんなり決まっていきました。 デザインを形にする 方針が決まれば、あとは画面のデザインです。いままでの内容を考慮しデザインを複数案作成し、どの案がいいかを決定し、細かい部分を調整していきました。 そうしてできあがったのが現状のデザインです。(下のデザイン案だと左案が近しい形です) 今回のデザインで気をつけたことは、なるべく標準に合わせ開発の負担をへらす(ようにしたけど実際はどうだったかな)、ISIDらしい誠実さもありながら記事ひとつひとつを丁寧に扱うデザインにする、という2点です。 前段の4つの整理がなければ、この点にたどり着けませんでした。 みんなの意見をまとめ、整理して納得しながら進めていくことで、最終的にたどり着いた形も満足度の高いものに仕上げることができたと思います。 実装に関する具体的な内容については こちらの記事 でご紹介しています。 最後に 今回はデザインの整理ポイントを中心に、リニューアルの経緯をお話させていただきました。 ISIDのテックブログはとにかく中身で勝負!しております。きっと等身大のISIDを垣間見れると思いますので、是非ご一読くださいませ。 執筆: @g-uxdc 、レビュー: @higa ( Shodo で執筆されました )
アバター
はじめに BIツールで使い易いテーブルについて 設計ドキュメントからBIツールのモデルファイルを自動生成する LookMLのラベル機能 caseによるラベリング LookMLの継承機能 Viewファイル自動生成ツールの実装 ビルドスクリプト main関数の実装 Excelのパーズ処理 Viewファイルの書き出し処理 まとめ はじめに このエントリは、 Looker Advent Calendar 2022 における17日目の記事です。 本日は、基幹システムのような歴史的な蓄積がある既存のデータを後付けで可視化する際に問題となる項目のラベリングに関するノウハウを説明します。 蓄積のあるデータベースに対してLookerのようなBIツールを直接接続するだけではうまく分析できません。 それには、様々な理由があります。その中の一つはテーブル名や カラム名 が システム開発 にとって都合の良い表現になっており、BIツールにとって都合の良い表現にはなっていないことです。 例えば、以下のように既存のデータベースから何も変更せずにLookerにテーブルをインポートすると以下のようになります。 テーブル名や カラム名 にアルファベットとアンダースコアのみを使ってテーブルを定義することになっているシステムは多いんじゃないでしょうか。これは、現代的なウェブシステムの多くがエンドユーザーに対して直接データベースへのアクセスを許可するような構造にはなっていないからです。 BIツールで使い易いテーブルについて BIツールを使ってデータベースをエンドユーザーが直接見に行くことを前提に、使い易いテーブルとはどんなものか考えてみましょう。 まずテーブル名や カラム名 が問題領域に即した日本語表記になっていることは必須でしょう。そして、 カラム名 については、その意味や意図を簡易的に参照できるとより望ましいですね。 また、実装の都合で場合分けするために保存されている項目については、無味乾燥な数字やアルファベットではなく論理的な名称になっていることが望ましいでしょう。 例えば、私たちのシステムには、 ms_biz_prtnr というテーブルがあり、 hojin_kbn というカラムが含まれています。そこには、 01 、 02 、 03 などの値が保存されます。正直言って、このままでは意味が分かりませんよね。 これは 取引先マスタ というテーブルにある 法人区分 というカラムの話をしています。そして三つの値は、それぞれ 株式会社 、 有限会社 、 財団法人 を意味しています。 使い込まれた基幹システムには、恐らく数百から数千のテーブルがあり、その中に含まれるカラムは延べ数千にも及ぶでしょう。それをBIツールで分析できるようにするために、このような変換処理を手動で行っていると時間がいくらあっても足りません。 当然、基幹システム側は変化する会社の状況に応じて変更されていくわけですから、BIツールの都合でデータベースに対する変更を止められるはずもありません。 では、どうするのがいいでしょうか? 設計ドキュメントからBIツールのモデルファイルを自動生成する あまりに多くのテーブルを手作業でBIツール用に再定義していては、データ分析による価値を生みだす前にコストがかかりすぎてしまいます。分析環境の構築に大きなコストをかけてしまうと、それだけ大きな期待がかかります。 小さく始めて、徐々に大きな成果に結びつけるならできるかぎりコストをかけずに環境を作るのが望ましいでしょう。また、様々なBIツールが市場にはありますので、最適なものを目指して乗り換えていけるようにしたいものです。 というわけで、既存の設計成果物であるデータベース設計書からBIツール用のモデルファイルを自動生成しましょう。ISIDの場合は、テーブル設計書という Excel で記述された大量のファイルがありましたので、これを使います。 このファイルフォーマットについては詳細を説明しませんが、何かを感じ取って貰えると嬉しいです。 モデルファイルのフォーマットはできるかぎりテキスト形式のもので、その形式に関して仕様が公開されているものを使うのが良いでしょう。この条件を満たすBIツールとしてLookerがあります。 LookerのLookMLは yaml によく似たテキスト形式のファイルなので、gitや GitHub を使って簡単に構成管理できます。 特に説明はしませんが、LookMLに対するイメージを持ってもらうために少しだけ例をお見せしましょう。 ecommercestore.model.lkml connection: order_database include: "*.view.lkml" explore: orders { join: customers { sql_on: ${orders.customer_id} = ${customers.id} ;; } } orders.view.lkml view: orders { dimension: id { primary_key: yes type: number sql: ${TABLE}.id ;; } dimension: customer_id { sql: ${TABLE}.customer_id ;; } dimension: amount { type: number value_format: "0.00" sql: ${TABLE}.amount ;; } dimension_group: created { type: time timeframes: [date, week] sql: ${TABLE}.created_at ;; } measure: count { type: count # creates sql COUNT(orders.id) sql: ${id} ;; } } 見ての通りごく単純な構造のテキストファイルです。単純な文字列処理ができれば、このファイルを自動生成できることがお分かりいただけることでしょう。 LookMLのさらに詳細な説明は公式のドキュメントが分かり易いのでおすすめです。 LookMLの紹介 LookMLのラベル機能 自動生成するにあたって、押さえておくべきLookMLの機能を紹介しましょう。 まずは、ラベリング機能です。 といっても、それほど難しいことはありません。viewやdimensionには label というフィールドがあるので、そこに分かり易い名前を指定するだけです。 view: ms_biz_prtnr { label: "取引先マスタ" sql_table_name: `MS_BIZ_PRTNR`;; dimension: biz_prtnr_cd { label: "取引先コード" type: string sql: ${TABLE}.biz_prtnr_cd;; } ラベルを使うとこのようになります。分かり易くなりますね。 ラベルについてより詳細な仕様を知りたい方は公式ドキュメントをご覧ください。 label (for fields) caseによるラベリング テーブル名や カラム名 のラベリングが分かったら今度は値のラベリングです。 ここで使うのは、caseとwhenを組み合わせた条件分岐です。 view: ms_biz_prtnr { label: "取引先マスタ" sql_table_name: `MS_BIZ_PRTNR`;; dimension: hojin_kbn { label: "法人区分" type: string case: { when: { sql: ${TABLE}.hojin_kbn = "01";; label: "株式会社" } when: { sql: ${TABLE}.hojin_kbn = "02";; label: "有限会社" } when: { sql: ${TABLE}.hojin_kbn = "03";; label: "財団法人" } } } それぞれの値に対して、どんなラベルを表示するのか設定していますね。これを元にLookerは SQL のcase句を生成します。 ラベルのありなしで、どんな風に表示が変わるのか見てみましょう。データを見る人が良く使うであろう検索フィルターの表示を抜き出してあります。 ラベルを設定しない場合には、データベースに保存されている具体的な値である 01 や 02 といった値がどんな意味を持つのか詳細に理解していないとデータをフィルターできません。 一方でラベルを設定してあれば、候補の中から選ぶだけなので使い易くなります。 caseによるラベリングについてより詳細な仕様を知りたい方は公式ドキュメントをご覧ください。 case LookMLの継承機能 既存の設計ドキュメントからviewファイルを自動生成する際には、手書きするファイルと自動生成するファイルは分割するべきです。 そういう時に使える機能が継承(extends)です。継承を使うと複数のview定義を合成して一つのviewにできます。 まずは、継承元となるviewを /generated/views/__ms_biz_prtnr.view というパスに定義します。 内容としては、ラベルの宣言だけを並べたものです。この例ではviewの名前にすこし癖がありますが、自動生成を意図して奇妙な印象を受けるようにしてあります。 view: __ms_biz_prtnr { label: "取引先マスタ" # 1. dimension: biz_prtnr_cd { label: "取引先コード" # 2. } } 継承する側のview定義を見てみましょう。以下のようなコードになります。 include: "/generated/views/__ms_biz_prtnr.view" # 1. view: ms_biz_prtnr { extends: [__ms_biz_prtnr] # 2. sql_table_name: `MS_BIZ_PRTNR`;; dimension: biz_prtnr_cd { type: string sql: ${TABLE}.biz_prtnr_cd;; } } 継承元となるviewのファイルパスを指定してincludeします。これによって、分かれたファイルが単一のファイルであるかのように扱われます。 継承元となるviewの名前を指定します。 これによって、 ms_biz_prtnr と __ms_biz_prtnr を一つのviewとして混ざり合った状態になりました。結果的に以下のような宣言のviewになります。 view: ms_biz_prtnr { label: "取引先マスタ" # __ms_biz_prtnr の 1. から取りこまれる sql_table_name: `MS_BIZ_PRTNR`;; dimension: biz_prtnr_cd { label: "取引先コード" # __ms_biz_prtnr の 2. から取りこまれる type: string sql: ${TABLE}.biz_prtnr_cd;; } 継承についてより詳細な仕様を知りたい方は公式ドキュメントをご覧ください。 extends (for views) Viewファイル自動生成ツールの実装 今回は Java を使ってviewを定義するファイルを自動生成します。 なお、 Java を使った開発環境の構築方法については以前に書いた KotlinとGradleで始めるモダンなビルド環境 を参考にしてください。 ビルド スクリプト 依存ライブラリについて説明するために、ビルド スクリプト を紹介しましょう。 plugins { application } repositories.mavenCentral() dependencies { implementation("org.apache.poi:poi:5.2.3") // 1. implementation("info.picocli:picocli:4.7.0") // 2. annotationProcessor("info.picocli:picocli-codegen:4.7.0") // 2. } application { mainClass.set("jp.co.isid.example.looker.App") // 3. } Java で Excel ファイルを読みだすのであれば、実績のある Apache POIがおすすめです。 コマンドライン アプリケーションの引数をパーズするライブラリとしてはpicocliを使うと アノテーション を付けるだけでやりたいことが実現できるので非常におすすめです。 main関数を定義したクラスのクラス名を設定しています。 main関数の実装 picocliを使ったアプリケーションのブートストラップ部分の実装を紹介しましょう。 package jp.co.isid.example.looker; import picocli.CommandLine; import java.nio.file.*; import java.util.List; import java.util.concurrent.Callable; @CommandLine.Command(name = "modelgen", // 1. description = "generate looker view models") public class App implements Callable<Integer> { @CommandLine.Option(names = {"-o", "--output"}, // 2. description = "output dir of generated resources", // 2. defaultValue = "build/generated/views/") // 2. Path outputDir; @CommandLine.Parameters(paramLabel = "INPUTS", defaultValue = "tableDesc") // 3. List<Path> inputs; // 3. @Override public Integer call() throws Exception { var tables = new TableLoader().load(this.inputs); Files.createDirectories(this.outputDir); tables.stream().forEach(model -> new ViewWriter().writeTo(this.outputDir, model)); return 0; } public static void main(String[] args) { System.exit(new CommandLine(App.class).execute(args)); // 4. } } CommandLine.Command アノテーション は、picocli に対してこのクラスが コマンドライン アプリケーションのエントリポイントであることを知らせるための アノテーション です。nameにアプリケーションの名前、descriptionに説明を設定します。 CommandLine.Option アノテーション は、 コマンドライン 引数のうち name 属性で指定したオプションの内容をフィールドに格納するための アノテーション です。文字列からの型変換はpicocliが自動的に行います。 CommandLine.Parameters アノテーション は、 コマンドライン 引数のうちオプションとしては解釈されなかったものが全て格納されるフィールドを指定するための アノテーション です。ここでも、型変換は自動的に行われます。 Excel のパーズ処理 Excel ファイルのパーズ処理は、細かく追っていくと膨大になりますので、いくつかポイントになる部分だけ紹介します。 まずは、パーズした結果を格納するデータ構造の紹介です。Java16から使えるようになったrecordを使うと驚くほどシンプルに書けますね。正直言って、 Java でアクセサを明示的に書かずに済む日がくるとは思っていませんでした。 package jp.co.isid.example.looker; import java.nio.file.Path; import java.util.List; public record TableModel(String logicalName, String physicalName, Path sourceFilepath, List<ColumnModel> columns) { public record ColumnModel(int no, String logicalName, String physicalName, String dataType) { } } どうあっても、小綺麗にはなりえないということを、確認して貰える Excel をパーズするコードです。いくつか、ポイントになる部分だけ選んで説明します。 ここで読んでいる Excel ファイルは、一度お見せしたものですが分かり易いよう、もう一度お見せします。 package jp.co.isid.example.looker; import org.apache.poi.hssf.usermodel.*; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.ss.usermodel.*; import java.io.*; import java.nio.file.*; import java.util.*; import java.util.function.BiFunction; import java.util.stream.*; public class TableLoader { public List<TableModel> load(List<Path> roots) { var matcher = FileSystems.getDefault() .getPathMatcher("glob:**/*.xls"); // 1. return roots.stream().flatMap(path -> { try (var stream = Files.walk(path)) { return stream.filter(matcher::matches) .collect(Collectors.toList()).stream(); } catch (IOException e) { throw new UncheckedIOException(e); } }).map(this::parse).collect(Collectors.toList()); } public TableModel parse(Path target) { try (var fs = new POIFSFileSystem(target.toFile())) { var book = new HSSFWorkbook(fs); var sheet = book.getSheet("テーブル設計書"); var names = parseHeaders(sheet); var columns = parseColumns(sheet); return new TableModel(names.get(0), names.get(1), target, columns); // 2. } catch (IOException e) { throw new UncheckedIOException(e); } } List<String> parseHeaders(HSSFSheet sheet) {} // 3. 省略 List<TableModel.ColumnModel> parseColumns(HSSFSheet sheet) { var evaluator = sheet.getWorkbook() .getCreationHelper().createFormulaEvaluator(); // 4. var format = new DataFormatter(); BiFunction<Row, Integer, String> read = (row, index) -> // 5. format.formatCellValue(row.getCell(index), evaluator); return StreamSupport.stream(sheet.spliterator(), false) .filter(row -> { // 6. var no = read.apply(row, 0); var dd = read.apply(row, 1); return no != null && dd != null && no.matches("\\d+") && dd.matches("\\d+"); }) .map(row -> new TableModel.ColumnModel( Integer.parseInt(read.apply(row, 0)), read.apply(row, 3), read.apply(row, 4), read.apply(row, 5))) .sorted(Comparator.comparing(TableModel.ColumnModel::no)) // 7. .collect(Collectors.toList()); } } Java で ファイルシステム をトラバースしながらGLOB形式に一致したファイルを取りだす処理が、ここから始まる一連のコードです。Java1.7からある仕組みですが意外と使われていない API だと感じています。 recordを使って宣言したTableModelのコンスト ラク タを呼び出しています。呼び出し側は今まで通りのクラスに宣言されたコンスト ラク タを呼びだす時と同じですね。 私たちのテーブル設計書では、この部分にオートシェイプを使うなど煩雑なコードになっているので、説明を省略させてください。 Excel のセルに書かれている式を解決済みの状態で読みだすためには FormulaEvaluator を使います。 Excel のセルに書かれている日付書式を使ってフォーマット済みのデータを読みだすためには、 DataFormatter と組み合わせて使います。現代の Java で ラムダ式 を使うことでローカルスコープの関数をこのように定義できるので便利になりましたね。 Excel のシートの行を上から読みながらカラム定義が書いてある部分まで読み飛ばす処理です。実に泥臭いコードですね。 recordを使って宣言したColumnModelに自動生成されたアクセサをメソッド参照しています。recordではデータの読み出し用のメソッドに get が付いていないのでコードがシンプルですね。 Viewファイルの書き出し処理 最後は、ファイルを書きだすコードです。ここでは特別な事を何もしていません。 package jp.co.isid.example.looker; import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Path; public class ViewWriter { public void writeTo(Path dir, TableModel table) { try (var printer = new PrintWriter(dir.resolve(toFilename(table)).toFile(), StandardCharsets.UTF_8)) { printer.println(); printer.format("view: %s {%n", toViewName(table)); printer.format(" label: \"%s\"%n", table.logicalName()); table.columns().forEach(model -> printColumn(printer, model)); printer.println("}"); } catch (IOException e) { throw new UncheckedIOException(e); } } void printColumn(PrintWriter printer, TableModel.ColumnModel model) { printer.println(); printer.format(" dimension: %s {%n", model.physicalName().toLowerCase()); printer.format(" label: \"%s\"%n", model.logicalName()); printer.println(" }"); } String toViewName(TableModel model) { return "__" + model.physicalName().toLowerCase(); } String toFilename(TableModel model) { return toViewName(model) + ".view.lkml"; } } 標準 API を使って単にテキストを出力していますね。 まとめ このエントリでは、BIツールで使い易いデータベースと一般的な基幹システムにおけるデータベースにはギャップがあることを指摘しました。 既存の設計ドキュメントからコードの自動生成をすることで、このギャップを埋められることを実際に動作するコードと併せて説明しました。 このエントリを読んだ皆様が、よりよいデータの前処理が出来れば幸いです。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募をお待ちしています。 社内SE(DX推進エンジニア) 執筆: @sato.taichi 、レビュー: @handa.kenta ( Shodo で執筆されました )
アバター
はじめに BIツールで使い易いテーブルについて 設計ドキュメントからBIツールのモデルファイルを自動生成する LookMLのラベル機能 caseによるラベリング LookMLの継承機能 Viewファイル自動生成ツールの実装 ビルドスクリプト main関数の実装 Excelのパーズ処理 Viewファイルの書き出し処理 まとめ はじめに このエントリは、 Looker Advent Calendar 2022 における17日目の記事です。 本日は、基幹システムのような歴史的な蓄積がある既存のデータを後付けで可視化する際に問題となる項目のラベリングに関するノウハウを説明します。 蓄積のあるデータベースに対してLookerのようなBIツールを直接接続するだけではうまく分析できません。 それには、様々な理由があります。その中の一つはテーブル名や カラム名 が システム開発 にとって都合の良い表現になっており、BIツールにとって都合の良い表現にはなっていないことです。 例えば、以下のように既存のデータベースから何も変更せずにLookerにテーブルをインポートすると以下のようになります。 テーブル名や カラム名 にアルファベットとアンダースコアのみを使ってテーブルを定義することになっているシステムは多いんじゃないでしょうか。これは、現代的なウェブシステムの多くがエンドユーザーに対して直接データベースへのアクセスを許可するような構造にはなっていないからです。 BIツールで使い易いテーブルについて BIツールを使ってデータベースをエンドユーザーが直接見に行くことを前提に、使い易いテーブルとはどんなものか考えてみましょう。 まずテーブル名や カラム名 が問題領域に即した日本語表記になっていることは必須でしょう。そして、 カラム名 については、その意味や意図を簡易的に参照できるとより望ましいですね。 また、実装の都合で場合分けするために保存されている項目については、無味乾燥な数字やアルファベットではなく論理的な名称になっていることが望ましいでしょう。 例えば、私たちのシステムには、 ms_biz_prtnr というテーブルがあり、 hojin_kbn というカラムが含まれています。そこには、 01 、 02 、 03 などの値が保存されます。正直言って、このままでは意味が分かりませんよね。 これは 取引先マスタ というテーブルにある 法人区分 というカラムの話をしています。そして三つの値は、それぞれ 株式会社 、 有限会社 、 財団法人 を意味しています。 使い込まれた基幹システムには、恐らく数百から数千のテーブルがあり、その中に含まれるカラムは延べ数千にも及ぶでしょう。それをBIツールで分析できるようにするために、このような変換処理を手動で行っていると時間がいくらあっても足りません。 当然、基幹システム側は変化する会社の状況に応じて変更されていくわけですから、BIツールの都合でデータベースに対する変更を止められるはずもありません。 では、どうするのがいいでしょうか? 設計ドキュメントからBIツールのモデルファイルを自動生成する あまりに多くのテーブルを手作業でBIツール用に再定義していては、データ分析による価値を生みだす前にコストがかかりすぎてしまいます。分析環境の構築に大きなコストをかけてしまうと、それだけ大きな期待がかかります。 小さく始めて、徐々に大きな成果に結びつけるならできるかぎりコストをかけずに環境を作るのが望ましいでしょう。また、様々なBIツールが市場にはありますので、最適なものを目指して乗り換えていけるようにしたいものです。 というわけで、既存の設計成果物であるデータベース設計書からBIツール用のモデルファイルを自動生成しましょう。ISIDの場合は、テーブル設計書という Excel で記述された大量のファイルがありましたので、これを使います。 このファイルフォーマットについては詳細を説明しませんが、何かを感じ取って貰えると嬉しいです。 モデルファイルのフォーマットはできるかぎりテキスト形式のもので、その形式に関して仕様が公開されているものを使うのが良いでしょう。この条件を満たすBIツールとしてLookerがあります。 LookerのLookMLは yaml によく似たテキスト形式のファイルなので、gitや GitHub を使って簡単に構成管理できます。 特に説明はしませんが、LookMLに対するイメージを持ってもらうために少しだけ例をお見せしましょう。 ecommercestore.model.lkml connection: order_database include: "*.view.lkml" explore: orders { join: customers { sql_on: ${orders.customer_id} = ${customers.id} ;; } } orders.view.lkml view: orders { dimension: id { primary_key: yes type: number sql: ${TABLE}.id ;; } dimension: customer_id { sql: ${TABLE}.customer_id ;; } dimension: amount { type: number value_format: "0.00" sql: ${TABLE}.amount ;; } dimension_group: created { type: time timeframes: [date, week] sql: ${TABLE}.created_at ;; } measure: count { type: count # creates sql COUNT(orders.id) sql: ${id} ;; } } 見ての通りごく単純な構造のテキストファイルです。単純な文字列処理ができれば、このファイルを自動生成できることがお分かりいただけることでしょう。 LookMLのさらに詳細な説明は公式のドキュメントが分かり易いのでおすすめです。 LookMLの紹介 LookMLのラベル機能 自動生成するにあたって、押さえておくべきLookMLの機能を紹介しましょう。 まずは、ラベリング機能です。 といっても、それほど難しいことはありません。viewやdimensionには label というフィールドがあるので、そこに分かり易い名前を指定するだけです。 view: ms_biz_prtnr { label: "取引先マスタ" sql_table_name: `MS_BIZ_PRTNR`;; dimension: biz_prtnr_cd { label: "取引先コード" type: string sql: ${TABLE}.biz_prtnr_cd;; } ラベルを使うとこのようになります。分かり易くなりますね。 ラベルについてより詳細な仕様を知りたい方は公式ドキュメントをご覧ください。 label (for fields) caseによるラベリング テーブル名や カラム名 のラベリングが分かったら今度は値のラベリングです。 ここで使うのは、caseとwhenを組み合わせた条件分岐です。 view: ms_biz_prtnr { label: "取引先マスタ" sql_table_name: `MS_BIZ_PRTNR`;; dimension: hojin_kbn { label: "法人区分" type: string case: { when: { sql: ${TABLE}.hojin_kbn = "01";; label: "株式会社" } when: { sql: ${TABLE}.hojin_kbn = "02";; label: "有限会社" } when: { sql: ${TABLE}.hojin_kbn = "03";; label: "財団法人" } } } それぞれの値に対して、どんなラベルを表示するのか設定していますね。これを元にLookerは SQL のcase句を生成します。 ラベルのありなしで、どんな風に表示が変わるのか見てみましょう。データを見る人が良く使うであろう検索フィルターの表示を抜き出してあります。 ラベルを設定しない場合には、データベースに保存されている具体的な値である 01 や 02 といった値がどんな意味を持つのか詳細に理解していないとデータをフィルターできません。 一方でラベルを設定してあれば、候補の中から選ぶだけなので使い易くなります。 caseによるラベリングについてより詳細な仕様を知りたい方は公式ドキュメントをご覧ください。 case LookMLの継承機能 既存の設計ドキュメントからviewファイルを自動生成する際には、手書きするファイルと自動生成するファイルは分割するべきです。 そういう時に使える機能が継承(extends)です。継承を使うと複数のview定義を合成して一つのviewにできます。 まずは、継承元となるviewを /generated/views/__ms_biz_prtnr.view というパスに定義します。 内容としては、ラベルの宣言だけを並べたものです。この例ではviewの名前にすこし癖がありますが、自動生成を意図して奇妙な印象を受けるようにしてあります。 view: __ms_biz_prtnr { label: "取引先マスタ" # 1. dimension: biz_prtnr_cd { label: "取引先コード" # 2. } } 継承する側のview定義を見てみましょう。以下のようなコードになります。 include: "/generated/views/__ms_biz_prtnr.view" # 1. view: ms_biz_prtnr { extends: [__ms_biz_prtnr] # 2. sql_table_name: `MS_BIZ_PRTNR`;; dimension: biz_prtnr_cd { type: string sql: ${TABLE}.biz_prtnr_cd;; } } 継承元となるviewのファイルパスを指定してincludeします。これによって、分かれたファイルが単一のファイルであるかのように扱われます。 継承元となるviewの名前を指定します。 これによって、 ms_biz_prtnr と __ms_biz_prtnr を一つのviewとして混ざり合った状態になりました。結果的に以下のような宣言のviewになります。 view: ms_biz_prtnr { label: "取引先マスタ" # __ms_biz_prtnr の 1. から取りこまれる sql_table_name: `MS_BIZ_PRTNR`;; dimension: biz_prtnr_cd { label: "取引先コード" # __ms_biz_prtnr の 2. から取りこまれる type: string sql: ${TABLE}.biz_prtnr_cd;; } 継承についてより詳細な仕様を知りたい方は公式ドキュメントをご覧ください。 extends (for views) Viewファイル自動生成ツールの実装 今回は Java を使ってviewを定義するファイルを自動生成します。 なお、 Java を使った開発環境の構築方法については以前に書いた KotlinとGradleで始めるモダンなビルド環境 を参考にしてください。 ビルド スクリプト 依存ライブラリについて説明するために、ビルド スクリプト を紹介しましょう。 plugins { application } repositories.mavenCentral() dependencies { implementation("org.apache.poi:poi:5.2.3") // 1. implementation("info.picocli:picocli:4.7.0") // 2. annotationProcessor("info.picocli:picocli-codegen:4.7.0") // 2. } application { mainClass.set("jp.co.isid.example.looker.App") // 3. } Java で Excel ファイルを読みだすのであれば、実績のある Apache POIがおすすめです。 コマンドライン アプリケーションの引数をパーズするライブラリとしてはpicocliを使うと アノテーション を付けるだけでやりたいことが実現できるので非常におすすめです。 main関数を定義したクラスのクラス名を設定しています。 main関数の実装 picocliを使ったアプリケーションのブートストラップ部分の実装を紹介しましょう。 package jp.co.isid.example.looker; import picocli.CommandLine; import java.nio.file.*; import java.util.List; import java.util.concurrent.Callable; @CommandLine.Command(name = "modelgen", // 1. description = "generate looker view models") public class App implements Callable<Integer> { @CommandLine.Option(names = {"-o", "--output"}, // 2. description = "output dir of generated resources", // 2. defaultValue = "build/generated/views/") // 2. Path outputDir; @CommandLine.Parameters(paramLabel = "INPUTS", defaultValue = "tableDesc") // 3. List<Path> inputs; // 3. @Override public Integer call() throws Exception { var tables = new TableLoader().load(this.inputs); Files.createDirectories(this.outputDir); tables.stream().forEach(model -> new ViewWriter().writeTo(this.outputDir, model)); return 0; } public static void main(String[] args) { System.exit(new CommandLine(App.class).execute(args)); // 4. } } CommandLine.Command アノテーション は、picocli に対してこのクラスが コマンドライン アプリケーションのエントリポイントであることを知らせるための アノテーション です。nameにアプリケーションの名前、descriptionに説明を設定します。 CommandLine.Option アノテーション は、 コマンドライン 引数のうち name 属性で指定したオプションの内容をフィールドに格納するための アノテーション です。文字列からの型変換はpicocliが自動的に行います。 CommandLine.Parameters アノテーション は、 コマンドライン 引数のうちオプションとしては解釈されなかったものが全て格納されるフィールドを指定するための アノテーション です。ここでも、型変換は自動的に行われます。 Excel のパーズ処理 Excel ファイルのパーズ処理は、細かく追っていくと膨大になりますので、いくつかポイントになる部分だけ紹介します。 まずは、パーズした結果を格納するデータ構造の紹介です。Java16から使えるようになったrecordを使うと驚くほどシンプルに書けますね。正直言って、 Java でアクセサを明示的に書かずに済む日がくるとは思っていませんでした。 package jp.co.isid.example.looker; import java.nio.file.Path; import java.util.List; public record TableModel(String logicalName, String physicalName, Path sourceFilepath, List<ColumnModel> columns) { public record ColumnModel(int no, String logicalName, String physicalName, String dataType) { } } どうあっても、小綺麗にはなりえないということを、確認して貰える Excel をパーズするコードです。いくつか、ポイントになる部分だけ選んで説明します。 ここで読んでいる Excel ファイルは、一度お見せしたものですが分かり易いよう、もう一度お見せします。 package jp.co.isid.example.looker; import org.apache.poi.hssf.usermodel.*; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.ss.usermodel.*; import java.io.*; import java.nio.file.*; import java.util.*; import java.util.function.BiFunction; import java.util.stream.*; public class TableLoader { public List<TableModel> load(List<Path> roots) { var matcher = FileSystems.getDefault() .getPathMatcher("glob:**/*.xls"); // 1. return roots.stream().flatMap(path -> { try (var stream = Files.walk(path)) { return stream.filter(matcher::matches) .collect(Collectors.toList()).stream(); } catch (IOException e) { throw new UncheckedIOException(e); } }).map(this::parse).collect(Collectors.toList()); } public TableModel parse(Path target) { try (var fs = new POIFSFileSystem(target.toFile())) { var book = new HSSFWorkbook(fs); var sheet = book.getSheet("テーブル設計書"); var names = parseHeaders(sheet); var columns = parseColumns(sheet); return new TableModel(names.get(0), names.get(1), target, columns); // 2. } catch (IOException e) { throw new UncheckedIOException(e); } } List<String> parseHeaders(HSSFSheet sheet) {} // 3. 省略 List<TableModel.ColumnModel> parseColumns(HSSFSheet sheet) { var evaluator = sheet.getWorkbook() .getCreationHelper().createFormulaEvaluator(); // 4. var format = new DataFormatter(); BiFunction<Row, Integer, String> read = (row, index) -> // 5. format.formatCellValue(row.getCell(index), evaluator); return StreamSupport.stream(sheet.spliterator(), false) .filter(row -> { // 6. var no = read.apply(row, 0); var dd = read.apply(row, 1); return no != null && dd != null && no.matches("\\d+") && dd.matches("\\d+"); }) .map(row -> new TableModel.ColumnModel( Integer.parseInt(read.apply(row, 0)), read.apply(row, 3), read.apply(row, 4), read.apply(row, 5))) .sorted(Comparator.comparing(TableModel.ColumnModel::no)) // 7. .collect(Collectors.toList()); } } Java で ファイルシステム をトラバースしながらGLOB形式に一致したファイルを取りだす処理が、ここから始まる一連のコードです。Java1.7からある仕組みですが意外と使われていない API だと感じています。 recordを使って宣言したTableModelのコンスト ラク タを呼び出しています。呼び出し側は今まで通りのクラスに宣言されたコンスト ラク タを呼びだす時と同じですね。 私たちのテーブル設計書では、この部分にオートシェイプを使うなど煩雑なコードになっているので、説明を省略させてください。 Excel のセルに書かれている式を解決済みの状態で読みだすためには FormulaEvaluator を使います。 Excel のセルに書かれている日付書式を使ってフォーマット済みのデータを読みだすためには、 DataFormatter と組み合わせて使います。現代の Java で ラムダ式 を使うことでローカルスコープの関数をこのように定義できるので便利になりましたね。 Excel のシートの行を上から読みながらカラム定義が書いてある部分まで読み飛ばす処理です。実に泥臭いコードですね。 recordを使って宣言したColumnModelに自動生成されたアクセサをメソッド参照しています。recordではデータの読み出し用のメソッドに get が付いていないのでコードがシンプルですね。 Viewファイルの書き出し処理 最後は、ファイルを書きだすコードです。ここでは特別な事を何もしていません。 package jp.co.isid.example.looker; import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Path; public class ViewWriter { public void writeTo(Path dir, TableModel table) { try (var printer = new PrintWriter(dir.resolve(toFilename(table)).toFile(), StandardCharsets.UTF_8)) { printer.println(); printer.format("view: %s {%n", toViewName(table)); printer.format(" label: \"%s\"%n", table.logicalName()); table.columns().forEach(model -> printColumn(printer, model)); printer.println("}"); } catch (IOException e) { throw new UncheckedIOException(e); } } void printColumn(PrintWriter printer, TableModel.ColumnModel model) { printer.println(); printer.format(" dimension: %s {%n", model.physicalName().toLowerCase()); printer.format(" label: \"%s\"%n", model.logicalName()); printer.println(" }"); } String toViewName(TableModel model) { return "__" + model.physicalName().toLowerCase(); } String toFilename(TableModel model) { return toViewName(model) + ".view.lkml"; } } 標準 API を使って単にテキストを出力していますね。 まとめ このエントリでは、BIツールで使い易いデータベースと一般的な基幹システムにおけるデータベースにはギャップがあることを指摘しました。 既存の設計ドキュメントからコードの自動生成をすることで、このギャップを埋められることを実際に動作するコードと併せて説明しました。 このエントリを読んだ皆様が、よりよいデータの前処理が出来れば幸いです。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募をお待ちしています。 社内SE(DX推進エンジニア) 執筆: @sato.taichi 、レビュー: @handa.kenta ( Shodo で執筆されました )
アバター
みなさんこんにちは。 電通国際情報サービス (ISID) 金融ソリューション事業部の水野です。 これは 電通国際情報サービス Advent Calendar 2022 の16日目の記事です。 今回は、ISID金融事業部で運用しているスキルマップについてご紹介します。 テッ クリード とは 実は、ISIDの少なくとも金融事業部にテッ クリード と言うポジションはありません。 実在するのはチーフアーキテクトと言う職種のみで、各プロジェクトでリードエンジニアやテッ クリード という仮想的なロールがあるのが実態です。 一時期は フルスタ ックエンジニアと呼んでいる時期もありましたが、近年このワーディングが好まれない印象なので、大々的に使っていません。 主観ですが、 フルスタ ックエンジニアはインフラ知識/運用系の知識のウェイトが高いエンジニアで、テッ クリード はソフトウェア アーキテクチャ 、Webアプリケーション実装技術寄りのエンジニアという印象を持っているので、 フルスタ ックエンジニアと使い分けても良いと考えてはいます。 フルスタ ックエンジニアより、最近はフルサイクルエンジニア( 1 )の方が一般的かもしれません。 アーキテクトやテッ クリード などいろいろ呼び名はありますが、以下の職務をこなすエンジニアと定義しています。 システム アーキテクチャ ( 2 )、ソフトウェア アーキテクチャ の規定 技術的な難易度、重要度が高い箇所の実装 フレームワーク 部分やコア機能の実装など、システムの根幹部分の実装 プロトタイピング 技術課題の解消 技術的な意思決定 テク ノロ ジスタックの選定 開発手法、プロセスの策定 品質保証戦略立案(例えば ユニットテスト ) 要件、仕様面の技術的な見地からの判断 技術系チームの組成、運営、醸成 エンジニア育成 守備範囲はアプリケーション基盤チームやインフラチーム 技術者代表として内外の ステークホルダー との合意形成 クラウド 領域を主戦場にするシニアエンジニアは、 クラウド アーキテクトと呼んだりすることもありますね。 スキルマップは、上述の職務をこなす為にどの程度の習熟度が必要なのかを測ることを目的としています。 スキルマップ 前提 項目ごとに個別具体的な説明を付与するのは辛いので、以下の前提を置いています。 コンピュータサイエンス 、 ソフトウェア工学 の基礎は、 IPA応用情報試験 取得による担保 システムアーキテクト 取得が望ましいが、必須取得は 応用情報技術者 具体的な言語、プラットフォームにフォーカスせず、根本的な知識・理解度を図る 一部、特定の言語や クラウド ベンダーに触れている箇所はある ステークホルダー との調整など、技術者であっても必要なコミュニケーションスキルは存在するが、相対的にウェイトが低いので マネジメントスキル に全て押し込む どの項目をどれだけ習得すればテッ クリード という明確な基準はないが、テッ クリード とは深く広範な知識があるもの 上記の前提のほか、ISID金融の業務に依存した構成になっている部分もあります。 なお、資格試験と言うと敬遠しがちな方も多いですが、意外と良問が多く、基本的・体系的なITスキルを身に付けるには有用です。 問題もアップデートされており、例えば最近では クラウド や 機械学習 に関する出題もあり、化石過ぎて実戦で使えない知識と言う訳ではなく、むしろエンジニアなら最低限習得しておくべき素養と考えます。 スキルマップ プログラミングスキル 構造化プログラミング 変数の概念(整数、文字列、真偽、配列) 演算 制御構造(順次、分岐、反復) 関数 オブジェクト指向 プログラミング カプセル化 ポリモーフィズム 差分プログラミングによる効率化 関数型プログラミング 参照透過性 クロージャ 関数合成、部分適用 高階関数 実践プログラミング基礎 DBアクセス 業務モデルを実装に落とし込むスキル エラー処理一般 Webアプリケーション実装 プロトタイピングによる ユースケース の具現化スキル プログラミング運用スキル Linting(text lintを含む) ビルド デバッグ 、ログ参照/解析からの問題解決 IDE の知識を含む、開発環境構築スキル プラグイン の選定と導入、設定 Docker Composeによる仮想化やDocker Desktop利用など フォーマッティング、lint設定、保存時アクション等、開発効率を高める工夫 リファ レンシング (公式リファレンスへ辿り着く能力、参照情報の本質的な理解と問題解決能力) 主要 プログラミング言語 のツールチェインの知見と導入スキル レビュー 静的解析ツールを使いこなし、レポートの評価/運用/フィードバック コードレビュー C/Iとの統合スキル SaaS型C/Iの利活用と運用 プロジェクト特性に合わせた応用力 中級 プログラマー レベル 利用頻度高の言語に対する一定の習熟度(文法、特性、利用シーンの想像力) マルチスレッディング 再帰 等の アルゴリズム による効率化 依存性低減力 AOP 、MIXIN 可読性、保守性の高いコードを書くスキル 安定性重視(副作用がないコーディングの理解、意識と実践) 上級 プログラマ メタプログラミング 非同期ノン ブロッキング I/O 高度な並列性に対する素養、実装力、 OSS や手法に関する知見 マイクロバッチ、ストリーム処理 コンテナベースの並列化 並列化を備えたプログラミングモデル Rustなどの比較的新しいが将来性がありそうな要素技術へのアンテナ ビッグデータ の取り扱いを可能にするプログラミングスキル オーケストレーション レイヤの勘所や、サービスメッシュなどの伸び盛りな分野のキャッチアップ 論理型言語の知識 フロントエンド UIデザイン知識(専業デザイナーとコンテキストを共有し、同じ言語で会話できるレベル) SPA実装スキル CSS 関連テク ノロ ジ/運用 パラダイム テク ノロ ジスタックの選定センス、運用組込み。例えばnpmライブラリの選択など WASMに典型的な、注目度が高い新技術領域のキャッチアップ トランスパイル、ミニファイ、広義のビルド、デプロイを中心としたC/Iスキル SEO に関する専門知識、スコアを上げるテクニック 機械学習 教師あり学習 /なし学習の理解 回帰、決定木、 SVM 、 クラスタリング 等、基本的な アルゴリズム の理解 アルゴリズム が明確であれば、 Python 等である程度実装可能な能力 データサイエンティストとの会話が成立する程度の知識 機械学習 をシステムに組み込む際の アーキテクチャ 策定および導入計画立案力 業務ではおそらく使わないが、何かで強みになるかもしれない ギーク 領域 アセンブラ FPGA カーネル 実装力 HDL 大凡常人が会得していないであろうコア感/レア感溢れるダークサイド・テク ノロ ジ アーキテクティング オニオン アーキテクチャ 、クリーン アーキテクチャ などのソフトウェア アーキテクチャ に関する知識 マイクロサービス アーキテクチャ 、ミニサービス アーキテクチャ などの アーキテクチャパターン Micro Frontends 処理方式毎の類型化能力 近代的なアーキテクチャ 設計スキル 共 通化 /再利用性を念頭に置いた全体設計 オブジェクト指向設計 デザインパターン に関する知識 DDDなどのモデル駆動設計 SaaS を効率的に利活用するデザインスキル テスト ユニットテスト 言語毎の ユニットテスト 実装力 ユニットテスト フレームワーク に関する知見 ユニットテスト 戦略・計画立案/ プログラマー への浸透推進力 リグレッション テストと リファクタリング ケース設計 JSTQB 同値分割 境界値分析 CFD技法 エラー推測/異常系の考慮 非機能テスト 性能テスト 計画、特に準備 ツール選定 シナリオ作成 ボトルネック 抽出 メモリリーク テスト 負荷試験 長期運用安定性 システム運用テスト 耐障害性試験 自動化 バックエンド E2E 結合テスト は別の素養という整理 永続化層 集合演算 RDB 系知識 KVS系知識 論理ER、物理ER インデキシング等のパフォーマンスの考慮 スキーマ マイグレーション アルゴリズム 、データ構造 データ構造:ツリー、キュー、スタック ソート( IPA 試験で最低限は充足される) 探索( IPA 試験で最低限は充足される) システム基盤(インフラスト ラク チャ/システム アーキテクチャ ) IPA非機能要求グレード の理解および定義スキル 可用性( 稼働率 向上、耐障害性、災害対策、回復等の基礎スキル) 性能・拡張性(処理量、目標値設計、リソース拡張) 運用・保守性(バックアップ、パッチ適用、時刻同期、監視、障害時運用等の基礎素養) 移行性(移行方式設計、データ量設計、インフラ移行計画) セキュリティ(アクセス制限、データ秘匿、 マルウェア 対策等基本的な対策に関する知識) システム環境・ エコロジー (制約/特性/適合/設置条件/環境マネジメント) HWに関する基礎知識 CPU、メモリ、ディスク( RAID 込み)の IPA 応用情報試験レベルの知識 GPU 、MRAM、ReRAM、NRAMなど、比較的新しめのハードウェアに関する知見 ルータなどのネットワーク機器 OW/MW UNIX 系サーバ Windows サーバ RDB 製品導入および管理スキル ジョブスケジューラ 初見のMWでも、マニュアルを読んで理解し、設計/インテグレーション可能なスキル ネットワーク TCP/IP ルーティング スイッチング OSI基本参照モデル と対応HW、 プロトコル の理解および仕様を調査可能な能力 CDN 仮想化 ハイパーバイザ OCIランタイム サーバレス DNS ドメイン (名前解決、ゾーン、ネームサーバ) リ ゾル バ、 フォワ ーダ 再帰 問い合わせ/反復問い合わせ DNS サーバ実装(BIND等)の理解 DNS サーバ構築力 インフラレイヤの高度なセキュリティ知識 ネットワークセキュリティ(FW、Proxy、 DMZ 、IPS) DDoS対策 暗号化 セキュリティ製品/ SaaS の選定力・導入スキル 認証、アクセス制限 情報セキュリティ(資産選定、管理、保護に関する知見) 監視(ログ監視、不正検知、追跡等) 監視/通知に関する各種手段の知識と実装力 不正アクセス 検知 トレーサビリティ確保の仕組み構築能力 SaaS (Datadog、New Relic等を想定)の知見 監視に関わる運用設計および導入推進力 クラウド 基礎 AWS であれば、IAM/ VPC /EC2/Lambda/RDS/ECS等の基本知識 GCP 、Azureなど、 AWS 以外の主要 クラウド プラットフォームに対する概要レベルの素養 Kubernetesおよびエコシステム 設計・構築 運用 エコシステムに関する知見 MLOps, DevOps Gitブランチモデル(git-flow| github -flow|gitlab-flow)の理解と設計、導入 IaC(メジャーどころではTerraform, AWS CDK | Cloudformation, Google Cloud Deployment Manager) コンテナ基盤知識、構築 CI/CDパイプライン ジョブフロー設計、バッチ制御 ロギング/モニタリング MLワークフロー設計、構築 Auto ML領域 開発プロセス 共通フレームに関する知見 SI開発向け 開発プロセス 策定スキル( ウォーターフォール 想定) 品質の作り込みおよび品質保証を念頭に置いたプロセス設計力 開発標準化能力(ガイド、運用推進/自動化、工程作業計画立案) 成果物体系構築スキルおよび設計ツール アジャイル 開発に関する知識 マネジ メントス キル 最低限のテンプレPM力 チームビルディング チーム運営 成果物体系策定および成果物定義 指導/スキルトランスファ ウォーターフォール 、 アジャイル 開発の一定の理解(実践力は別) 補足 ビルド 様々な言語のビルドの仕組み、ツールチェインに関する知見に加え、Bazelと言ったこの先必要になりそうなツールの知識も含みます。 SaaS型C/Iの利活用と運用 最近は専らGitHubActionを利用しています。一方、今後 クラウド プラットフォームのC/Iサービス利用の増加も考えられるので、 クラウド ベンダーのマネージドサービスの知識も含みます。 利用頻度高の言語に対する一定の習熟度 金融事業部では Java 、.NET( C# )が多く、次点で Python という肌感覚で、これらを利用頻度高に位置付けています。 最近では一定の規模の開発にgoが採用されることも増え、研究開発ではRustの利用も始めました。 依存性低減力 参照が循環していないなど、モジュール依存関係が複雑過ぎないプログラム設計および実装を意味します。 モジュール間の依存性を極力排すことを目的とし、DIコンテナによる実現などが考えられます。 トランスパイル、ミニファイ、広義のビルド、デプロイを中心としたC/Iスキル フロントエンドのビルド周りは苦手な人が多い印象で、Webpack、Turbopackなどツールチェインの習熟度を含みます。Romeなど新しめのツールチェインの知見も含みます。 近代的なアーキテクチャ 相当に弊社弊事業部の主観に満ち溢れたものですが、例えば Cloudflare workers+D1+Postgres WASMで、 CDN エッジサイドだけで何かを実装するみたいなものをイメージしています。 CDN FastlyやCloudflareなど3rd Partyを含みます。 OCIランタイム 開発ではDockerを利用するケースが多いですが、OCI界隈の動向や各種OCIランタイム実装の知見です。 Kubernetesおよびエコシステム どの クラウド プラットフォームを使おうとも、 Kubernetes が選択肢の一つとして上がる事が普通になりました。 インフラを専門領域とするエンジニアは、周辺エコシステムを含めて深い知識・理解が必要な時代です。 成果物体系構築スキルおよび設計ツール 設計ツールとは、 MarkDown を書くエディタや画像描画ツールなどが該当します。 画像描画ツールはdraw.ioが大半ですが、最近ではmermaidも徐々に増えてきています。 アーキテクチャ 系ドキュメントを書く際は、アーキチームのみでPlantUMLを採用するケースもあります。 成果物体系に見合った設計ツールを選定し、プロジェクト内で統一します。 運用 上記スキルマップの最下層の項目に対し、個人個人が自己申告でレベルを入力します。 レベル(習熟度)は4段階で、以下が基準です。 Lv1 触ったことがない Lv2 業務で触ったことがある Lv3 本質を理解し、使いこなしている Lv4 本質を深淵まで理解し、適切なシーンで応用可能 自己申告したレベルは承認制で、上位技術者あるいは上席がOKならば確定、NGならばフィードバックします。 とは言え、実際これを継続的に運用するのは難しく、課題も多いです。 運用上の課題で私が認識しているものは下記です。 事業部の全員がレベル設定している訳ではなく、技術系業務が中心の2割程度のエンジニアのみ実施 年度によっては実施していない これが一番最悪なので、2023年度は絶対にやる レベル設定は申告ベースのため、主観的になりがち。ただ、上位技術者によってある程度是正は可能 スキルマップの習熟度と技術系のポジションを、具体的にどう結びつけるかの解に至っていない どの領域をどのレベルまで修めればテッ クリード かなどの基準がない スキルマップ自体の更新は半年、出来れば3カ月程度で見直したい 個別要素技術に因らず、なるべく普遍的なスキルマップとなるよう志向しているので負荷は軽い まとめ テッ クリード は技術力で現場を牽引するのがミッションですが、技術に詳しい人が担う仮想的な職種であることが多いのではないでしょうか。 非常に重要な役割であるにもかかわらず、 インセンティブ や裁量の範囲など未定義なケースもあるかと思います。 組織によって「テッ クリード とは何か」は異なりますが、我々ISID金融ではその重要性を認識しており、可能な限りミッションとスキルセットを可視化しようと試みています。 また、テッ クリード に至るまでの道のりと、そもそも技術者が習得すべきスキルについての定義も必要と認識しています。 まだまだ道半ばではありますが、今回はスキルマップのご紹介でした。 最後までご覧になっていただき、誠にありがとうございました。 執筆: @mizuno.kazuhiro 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました ) https://netflixtechblog.com/full-cycle-developers-at-netflix-a08c31f83249 ↩ クラウド などのインフラスト ラク チャを含む、システム全体の構成 ↩
アバター
みなさんこんにちは。 電通国際情報サービス (ISID) 金融ソリューション事業部の水野です。 これは 電通国際情報サービス Advent Calendar 2022 の16日目の記事です。 今回は、ISID金融事業部で運用しているスキルマップについてご紹介します。 テッ クリード とは 実は、ISIDの少なくとも金融事業部にテッ クリード と言うポジションはありません。 実在するのはチーフアーキテクトと言う職種のみで、各プロジェクトでリードエンジニアやテッ クリード という仮想的なロールがあるのが実態です。 一時期は フルスタ ックエンジニアと呼んでいる時期もありましたが、近年このワーディングが好まれない印象なので、大々的に使っていません。 主観ですが、 フルスタ ックエンジニアはインフラ知識/運用系の知識のウェイトが高いエンジニアで、テッ クリード はソフトウェア アーキテクチャ 、Webアプリケーション実装技術寄りのエンジニアという印象を持っているので、 フルスタ ックエンジニアと使い分けても良いと考えてはいます。 フルスタ ックエンジニアより、最近はフルサイクルエンジニア( 1 )の方が一般的かもしれません。 アーキテクトやテッ クリード などいろいろ呼び名はありますが、以下の職務をこなすエンジニアと定義しています。 システム アーキテクチャ ( 2 )、ソフトウェア アーキテクチャ の規定 技術的な難易度、重要度が高い箇所の実装 フレームワーク 部分やコア機能の実装など、システムの根幹部分の実装 プロトタイピング 技術課題の解消 技術的な意思決定 テク ノロ ジスタックの選定 開発手法、プロセスの策定 品質保証戦略立案(例えば ユニットテスト ) 要件、仕様面の技術的な見地からの判断 技術系チームの組成、運営、醸成 エンジニア育成 守備範囲はアプリケーション基盤チームやインフラチーム 技術者代表として内外の ステークホルダー との合意形成 クラウド 領域を主戦場にするシニアエンジニアは、 クラウド アーキテクトと呼んだりすることもありますね。 スキルマップは、上述の職務をこなす為にどの程度の習熟度が必要なのかを測ることを目的としています。 スキルマップ 前提 項目ごとに個別具体的な説明を付与するのは辛いので、以下の前提を置いています。 コンピュータサイエンス 、 ソフトウェア工学 の基礎は、 IPA応用情報試験 取得による担保 システムアーキテクト 取得が望ましいが、必須取得は 応用情報技術者 具体的な言語、プラットフォームにフォーカスせず、根本的な知識・理解度を図る 一部、特定の言語や クラウド ベンダーに触れている箇所はある ステークホルダー との調整など、技術者であっても必要なコミュニケーションスキルは存在するが、相対的にウェイトが低いので マネジメントスキル に全て押し込む どの項目をどれだけ習得すればテッ クリード という明確な基準はないが、テッ クリード とは深く広範な知識があるもの 上記の前提のほか、ISID金融の業務に依存した構成になっている部分もあります。 なお、資格試験と言うと敬遠しがちな方も多いですが、意外と良問が多く、基本的・体系的なITスキルを身に付けるには有用です。 問題もアップデートされており、例えば最近では クラウド や 機械学習 に関する出題もあり、化石過ぎて実戦で使えない知識と言う訳ではなく、むしろエンジニアなら最低限習得しておくべき素養と考えます。 スキルマップ プログラミングスキル 構造化プログラミング 変数の概念(整数、文字列、真偽、配列) 演算 制御構造(順次、分岐、反復) 関数 オブジェクト指向 プログラミング カプセル化 ポリモーフィズム 差分プログラミングによる効率化 関数型プログラミング 参照透過性 クロージャ 関数合成、部分適用 高階関数 実践プログラミング基礎 DBアクセス 業務モデルを実装に落とし込むスキル エラー処理一般 Webアプリケーション実装 プロトタイピングによる ユースケース の具現化スキル プログラミング運用スキル Linting(text lintを含む) ビルド デバッグ 、ログ参照/解析からの問題解決 IDE の知識を含む、開発環境構築スキル プラグイン の選定と導入、設定 Docker Composeによる仮想化やDocker Desktop利用など フォーマッティング、lint設定、保存時アクション等、開発効率を高める工夫 リファ レンシング (公式リファレンスへ辿り着く能力、参照情報の本質的な理解と問題解決能力) 主要 プログラミング言語 のツールチェインの知見と導入スキル レビュー 静的解析ツールを使いこなし、レポートの評価/運用/フィードバック コードレビュー C/Iとの統合スキル SaaS型C/Iの利活用と運用 プロジェクト特性に合わせた応用力 中級 プログラマー レベル 利用頻度高の言語に対する一定の習熟度(文法、特性、利用シーンの想像力) マルチスレッディング 再帰 等の アルゴリズム による効率化 依存性低減力 AOP 、MIXIN 可読性、保守性の高いコードを書くスキル 安定性重視(副作用がないコーディングの理解、意識と実践) 上級 プログラマ メタプログラミング 非同期ノン ブロッキング I/O 高度な並列性に対する素養、実装力、 OSS や手法に関する知見 マイクロバッチ、ストリーム処理 コンテナベースの並列化 並列化を備えたプログラミングモデル Rustなどの比較的新しいが将来性がありそうな要素技術へのアンテナ ビッグデータ の取り扱いを可能にするプログラミングスキル オーケストレーション レイヤの勘所や、サービスメッシュなどの伸び盛りな分野のキャッチアップ 論理型言語の知識 フロントエンド UIデザイン知識(専業デザイナーとコンテキストを共有し、同じ言語で会話できるレベル) SPA実装スキル CSS 関連テク ノロ ジ/運用 パラダイム テク ノロ ジスタックの選定センス、運用組込み。例えばnpmライブラリの選択など WASMに典型的な、注目度が高い新技術領域のキャッチアップ トランスパイル、ミニファイ、広義のビルド、デプロイを中心としたC/Iスキル SEO に関する専門知識、スコアを上げるテクニック 機械学習 教師あり学習 /なし学習の理解 回帰、決定木、 SVM 、 クラスタリング 等、基本的な アルゴリズム の理解 アルゴリズム が明確であれば、 Python 等である程度実装可能な能力 データサイエンティストとの会話が成立する程度の知識 機械学習 をシステムに組み込む際の アーキテクチャ 策定および導入計画立案力 業務ではおそらく使わないが、何かで強みになるかもしれない ギーク 領域 アセンブラ FPGA カーネル 実装力 HDL 大凡常人が会得していないであろうコア感/レア感溢れるダークサイド・テク ノロ ジ アーキテクティング オニオン アーキテクチャ 、クリーン アーキテクチャ などのソフトウェア アーキテクチャ に関する知識 マイクロサービス アーキテクチャ 、ミニサービス アーキテクチャ などの アーキテクチャパターン Micro Frontends 処理方式毎の類型化能力 近代的なアーキテクチャ 設計スキル 共 通化 /再利用性を念頭に置いた全体設計 オブジェクト指向設計 デザインパターン に関する知識 DDDなどのモデル駆動設計 SaaS を効率的に利活用するデザインスキル テスト ユニットテスト 言語毎の ユニットテスト 実装力 ユニットテスト フレームワーク に関する知見 ユニットテスト 戦略・計画立案/ プログラマー への浸透推進力 リグレッション テストと リファクタリング ケース設計 JSTQB 同値分割 境界値分析 CFD技法 エラー推測/異常系の考慮 非機能テスト 性能テスト 計画、特に準備 ツール選定 シナリオ作成 ボトルネック 抽出 メモリリーク テスト 負荷試験 長期運用安定性 システム運用テスト 耐障害性試験 自動化 バックエンド E2E 結合テスト は別の素養という整理 永続化層 集合演算 RDB 系知識 KVS系知識 論理ER、物理ER インデキシング等のパフォーマンスの考慮 スキーマ マイグレーション アルゴリズム 、データ構造 データ構造:ツリー、キュー、スタック ソート( IPA 試験で最低限は充足される) 探索( IPA 試験で最低限は充足される) システム基盤(インフラスト ラク チャ/システム アーキテクチャ ) IPA非機能要求グレード の理解および定義スキル 可用性( 稼働率 向上、耐障害性、災害対策、回復等の基礎スキル) 性能・拡張性(処理量、目標値設計、リソース拡張) 運用・保守性(バックアップ、パッチ適用、時刻同期、監視、障害時運用等の基礎素養) 移行性(移行方式設計、データ量設計、インフラ移行計画) セキュリティ(アクセス制限、データ秘匿、 マルウェア 対策等基本的な対策に関する知識) システム環境・ エコロジー (制約/特性/適合/設置条件/環境マネジメント) HWに関する基礎知識 CPU、メモリ、ディスク( RAID 込み)の IPA 応用情報試験レベルの知識 GPU 、MRAM、ReRAM、NRAMなど、比較的新しめのハードウェアに関する知見 ルータなどのネットワーク機器 OW/MW UNIX 系サーバ Windows サーバ RDB 製品導入および管理スキル ジョブスケジューラ 初見のMWでも、マニュアルを読んで理解し、設計/インテグレーション可能なスキル ネットワーク TCP/IP ルーティング スイッチング OSI基本参照モデル と対応HW、 プロトコル の理解および仕様を調査可能な能力 CDN 仮想化 ハイパーバイザ OCIランタイム サーバレス DNS ドメイン (名前解決、ゾーン、ネームサーバ) リ ゾル バ、 フォワ ーダ 再帰 問い合わせ/反復問い合わせ DNS サーバ実装(BIND等)の理解 DNS サーバ構築力 インフラレイヤの高度なセキュリティ知識 ネットワークセキュリティ(FW、Proxy、 DMZ 、IPS) DDoS対策 暗号化 セキュリティ製品/ SaaS の選定力・導入スキル 認証、アクセス制限 情報セキュリティ(資産選定、管理、保護に関する知見) 監視(ログ監視、不正検知、追跡等) 監視/通知に関する各種手段の知識と実装力 不正アクセス 検知 トレーサビリティ確保の仕組み構築能力 SaaS (Datadog、New Relic等を想定)の知見 監視に関わる運用設計および導入推進力 クラウド 基礎 AWS であれば、IAM/ VPC /EC2/Lambda/RDS/ECS等の基本知識 GCP 、Azureなど、 AWS 以外の主要 クラウド プラットフォームに対する概要レベルの素養 Kubernetesおよびエコシステム 設計・構築 運用 エコシステムに関する知見 MLOps, DevOps Gitブランチモデル(git-flow| github -flow|gitlab-flow)の理解と設計、導入 IaC(メジャーどころではTerraform, AWS CDK | Cloudformation, Google Cloud Deployment Manager) コンテナ基盤知識、構築 CI/CDパイプライン ジョブフロー設計、バッチ制御 ロギング/モニタリング MLワークフロー設計、構築 Auto ML領域 開発プロセス 共通フレームに関する知見 SI開発向け 開発プロセス 策定スキル( ウォーターフォール 想定) 品質の作り込みおよび品質保証を念頭に置いたプロセス設計力 開発標準化能力(ガイド、運用推進/自動化、工程作業計画立案) 成果物体系構築スキルおよび設計ツール アジャイル 開発に関する知識 マネジ メントス キル 最低限のテンプレPM力 チームビルディング チーム運営 成果物体系策定および成果物定義 指導/スキルトランスファ ウォーターフォール 、 アジャイル 開発の一定の理解(実践力は別) 補足 ビルド 様々な言語のビルドの仕組み、ツールチェインに関する知見に加え、Bazelと言ったこの先必要になりそうなツールの知識も含みます。 SaaS型C/Iの利活用と運用 最近は専らGitHubActionを利用しています。一方、今後 クラウド プラットフォームのC/Iサービス利用の増加も考えられるので、 クラウド ベンダーのマネージドサービスの知識も含みます。 利用頻度高の言語に対する一定の習熟度 金融事業部では Java 、.NET( C# )が多く、次点で Python という肌感覚で、これらを利用頻度高に位置付けています。 最近では一定の規模の開発にgoが採用されることも増え、研究開発ではRustの利用も始めました。 依存性低減力 参照が循環していないなど、モジュール依存関係が複雑過ぎないプログラム設計および実装を意味します。 モジュール間の依存性を極力排すことを目的とし、DIコンテナによる実現などが考えられます。 トランスパイル、ミニファイ、広義のビルド、デプロイを中心としたC/Iスキル フロントエンドのビルド周りは苦手な人が多い印象で、Webpack、Turbopackなどツールチェインの習熟度を含みます。Romeなど新しめのツールチェインの知見も含みます。 近代的なアーキテクチャ 相当に弊社弊事業部の主観に満ち溢れたものですが、例えば Cloudflare workers+D1+Postgres WASMで、 CDN エッジサイドだけで何かを実装するみたいなものをイメージしています。 CDN FastlyやCloudflareなど3rd Partyを含みます。 OCIランタイム 開発ではDockerを利用するケースが多いですが、OCI界隈の動向や各種OCIランタイム実装の知見です。 Kubernetesおよびエコシステム どの クラウド プラットフォームを使おうとも、 Kubernetes が選択肢の一つとして上がる事が普通になりました。 インフラを専門領域とするエンジニアは、周辺エコシステムを含めて深い知識・理解が必要な時代です。 成果物体系構築スキルおよび設計ツール 設計ツールとは、 MarkDown を書くエディタや画像描画ツールなどが該当します。 画像描画ツールはdraw.ioが大半ですが、最近ではmermaidも徐々に増えてきています。 アーキテクチャ 系ドキュメントを書く際は、アーキチームのみでPlantUMLを採用するケースもあります。 成果物体系に見合った設計ツールを選定し、プロジェクト内で統一します。 運用 上記スキルマップの最下層の項目に対し、個人個人が自己申告でレベルを入力します。 レベル(習熟度)は4段階で、以下が基準です。 Lv1 触ったことがない Lv2 業務で触ったことがある Lv3 本質を理解し、使いこなしている Lv4 本質を深淵まで理解し、適切なシーンで応用可能 自己申告したレベルは承認制で、上位技術者あるいは上席がOKならば確定、NGならばフィードバックします。 とは言え、実際これを継続的に運用するのは難しく、課題も多いです。 運用上の課題で私が認識しているものは下記です。 事業部の全員がレベル設定している訳ではなく、技術系業務が中心の2割程度のエンジニアのみ実施 年度によっては実施していない これが一番最悪なので、2023年度は絶対にやる レベル設定は申告ベースのため、主観的になりがち。ただ、上位技術者によってある程度是正は可能 スキルマップの習熟度と技術系のポジションを、具体的にどう結びつけるかの解に至っていない どの領域をどのレベルまで修めればテッ クリード かなどの基準がない スキルマップ自体の更新は半年、出来れば3カ月程度で見直したい 個別要素技術に因らず、なるべく普遍的なスキルマップとなるよう志向しているので負荷は軽い まとめ テッ クリード は技術力で現場を牽引するのがミッションですが、技術に詳しい人が担う仮想的な職種であることが多いのではないでしょうか。 非常に重要な役割であるにもかかわらず、 インセンティブ や裁量の範囲など未定義なケースもあるかと思います。 組織によって「テッ クリード とは何か」は異なりますが、我々ISID金融ではその重要性を認識しており、可能な限りミッションとスキルセットを可視化しようと試みています。 また、テッ クリード に至るまでの道のりと、そもそも技術者が習得すべきスキルについての定義も必要と認識しています。 まだまだ道半ばではありますが、今回はスキルマップのご紹介でした。 最後までご覧になっていただき、誠にありがとうございました。 執筆: @mizuno.kazuhiro 、レビュー: @yamashita.tsuyoshi ( Shodo で執筆されました ) https://netflixtechblog.com/full-cycle-developers-at-netflix-a08c31f83249 ↩ クラウド などのインフラスト ラク チャを含む、システム全体の構成 ↩
アバター
こんにちは!X イノベーション 本部  エンタープライズ XRセンターの加納です。 この記事は 電通国際情報サービス Advent Calendar 2022 の15日目の記事です。 もうすぐクリスマスですが、プレゼントの用意は出来ていますか?まだですか?まだですよね? そんな方のために大貫サンタさんが靴下から取り出したのは… HMD です! 「 HMD 」という単語にピンとくる方は少数派かもしれません。 HMD とはHead-Mount-Displayの略で、つまりは VR 体験をする際に頭に被る、アレのことです。 加納と大貫サンタは3D・ VR 関係の部署に在籍しているため、今日はそんな部署ならではの機材を紹介していこうと思います。 個人で使用されているものだけではなく、事業者向けによく使用される機種も比較に入れてご紹介いたします。 比較していく機種は以下です。 Meta Quest2 Meta Quest Pro VIVE Pro2 Varjo Aero Varjo XR-3 以上の5機種が今回被り比べの対象となります。 性能面ではさまざまな記事が出ていますので、この記事での主な比較点は 重さ サイズ 価格、入手難易度 メガネを装着したまま被れるか? を重視して被り比べをしていきたいと思います。ではいきましょう! Meta Quest2 Meta Quest Pro Vive Pro2 Varjo Aero Varjo XR-3 メガネとHMD 被りやすさ4位タイ 被りやすさ3位 被りやすさ2位 被りやすさ1位 まとめ Meta Quest2 世界で最も普及している HMD と言ったらコレなのではないでしょうか。上の画像ではデフォルトのゴムバンドをEliteストラップに付け替えています。 スタンドアローン ・外付けセンサー不要で稼働し、被った状態で外部を確認することもできます(白黒画像)。 スタンドアローン ではありますがPCに接続して動かすこともでき、Steamのゲームもプレイできます。 デフォルトのゴムバンド装着での重さは 529g (参考値) Eliteストラップ装着での重さは 650g (参考値) と、非常に軽量なのが特徴です。 被ってみるとこんな感じです。 大貫サンタ「いつものって感じ」 ExRCではメンバー全員がQuest2を所有しているため、被るとあーこれだこれーと思ってしまうんですよね。一番 稼働率 の高い HMD です。 お値段は 59,400円 (2022年12月現在/公式価格)とお求めになりやすい価格、かつ家電量販店でも販売しており入手難易度も低いため、クリスマスプレゼントの第一候補になるのではないでしょうか。 Meta Quest Pro 去る2022年10月に発売された HMD で、上に出てきたMeta Quest2の上位機種です。 重さは 718g (参考値) 似たような形状のEliteストラップ装着時から比較しても70g程度の増量ですが、被った感じはいかがでしょう? 大貫サンタ「前が薄い!帽子のままでも被りやすい!あとQuest2より軽く感じる。前後のバランスがいいのかな」 Quest Proでは、Quest2にあった頭頂部をおさえるゴムバンドがなくなったことで格段に被りやすくなりました。髪型も崩れにくくなってありがたいですね。 スタンドアローン ・外付けセンサー不要・PC接続可能なのは同じですが、外部確認カメラがカラーになったのが大きな変化です。これによりAR(拡張現実)的な使い方がしやすくなりました。 加えてアイト ラッキング (視点を読み取る)・フェイスト ラッキング (表情を読み取る)などが搭載され、できることの幅が広がりました。 顔と画面の隙間は広めで、隙間からは外の光が入ってきます。オプションパーツで埋めることも出来ますが、パススルーの精度が高く外部が鮮明に見えるためかARゴーグルのような感覚があり、個人的には開いたままでいいなあという印象です。 お値段は 226 ,800円 (2022年12月現在/公式価格)とお高め。ですが装着感もできることも大幅に変わっているため、高すぎ!といった印象はありません。 まだ家電量販店で現物を手に取ることは出来ませんが、Metaの公式通販から購入できるため入手難易度は低め。クリスマスに間に合うかは…お住いの地域次第かな?といったところです。 Vive Pro2 VR の先駆者、HTC Viveの上位製品です。Meta Quest(当時はOculus Quest)が登場するまでは、 VR といったらVive!という感じでしたね。Pro2は耳に可動式のヘッドホンがついているのが特徴的なデザインです。 重さは 846g (参考値/コードが机についた状態) Vive Pro2はPC接続で稼働し、稼働には外付けセンサー( ベースステーション )を必要とします。 大貫サンタ「Meta系と比べるとかなり大きく感じる。でも重さに偏りがなくて安定してる」 外付けセンサーと言うと煩雑な感じがするかもしれませんが、フルト ラッキング をしたい場合にはVive公式でトラッカーが別売りされていますので、外付けセンサーありの製品を選択するのがおすすめです。 お値段は 178,990円 (2022年12月現在/フルキット/公式価格)でセンサー( ベースステーション 2.0)が2台同梱されています。 家電量販店で見ることは少ないですが、公式通販で入手が可能です。クリスマスに間に合うかは…Meta Quest Proと同じく、お住いの地域次第でしょうか。 Varjo Aero フィンランド に本社を構えるVarjoの製品です。ここからは エンタープライズ 色が濃くなってくるため、個人で持っている方がぐっと減るのではないでしょうか。 重さは 720g (参考値/コードが机についた状態) と、Meta Quest Proとおよそ同じくらいの重さです。 こちらもPC接続で稼働し、稼働には外付けセンサーを必要とします。 大貫サンタ「前に重心がある感じ、でも全体がそんなに重くないから大丈夫」 Varjo Aeroは軽量ながらVive Pro2よりも表示解像度が高いです。解像度が高いだけではなく、中心に向かって27PPD~35PPD(PPD…1度あたりの ピクセル 数)へと解像度が高くなっていきます。 Varjo製品は自動でPD(瞳孔間距離)を調整してくれます。これにより鮮明に映像が見えるほか、 VR酔い もしにくくなります。 お値段は 316,800円 (2022年12月現在/公式価格)とお高め。ですがこの解像度を味わうと他の VR にはなかなか戻れないかもしれませんね。 注意していただきたいのが、Varjo Aeroは紹介する5機種のうち唯一外部確認(パススルー表示)ができないタイプの HMD だということです。解像度は高いですが、用途を十分に確認した上で検討してみてください。 家電量販店では見かけないですが、公式通販で購入が可能です。ただし後述のVarjo XR-3と同じく外部センサーの入手がしづらいため、トータルでの入手難易度はちょっと高めです。 Varjo XR-3 こちらもVarjo社の製品で、紹介する中では最もハイエンドな機種になります。 重さは 1008g (参考値/コードが机についた状態) 堂々の1㎏超え。見えますか、ごっつい排気口がきらめいているのを。XR-3は作動するとかなりの熱を持ちます。 大貫サンタ「ずしっと来る。圧倒的に重い…けど画質も圧倒的にいい」 外部確認できるカメラの精度が高く、一度被るとついつい外さずにカメラであれこれ済ませてしまいがちですが、これだけ重いものを長時間被っていると首が筋肉痛になりますので気を付けましょう。 Aeroは解像度が徐々に高くなる構造でしたが、XR-3は視野の中心にあたる部分に別のディスプレイを搭載しており、中心部は71PPDを誇ります。 なお視力2.0の人の目は、60PPDを超えると ピクセル を見分けることが出来なくなるそうです。ですのでXR-3は人間の目の解像度を超えた HMD であるといえます。とんでもないですね。 こちらもPC接続で稼働し、外付けセンサーがなくても稼働しますが精度を高めるためにはセンサーが必要です。 問題の入手難易度についてですが…XR-3は エンタープライズ 用途が主であるため 一般での販売はされていません 。また昨今の円安や 半導体 不足もあり、価格もはっきり示すことが出来ません。 だいたいコンパクトカーと同じくらいの値段 だと思っておいていただけると良いかと思います。恐ろしいですね。 Varjo Aeroの項目でも出てきたセンサー( ベースステーション )についてですが、現在単体での入手が困難となっています。ですのでセンサーを同梱しないがセンサーを必要とするタイプの HMD を購入される際には、Vive Pro2などHTC製品のフルキットを購入し ベースステーション を入手する必要があります。これも入手難易度を上げる一因となっています。 ExRC に問い合わせていただけたらまるごとご相談に乗れるんですけどね!(宣伝です!) メガネと HMD ここからは横道にそれて、メガネ使用者に優しい HMD はどれなのか?を紹介させていただけたらと思います。 ※完全に主観です。個人差やメガネの形状によって変わるものですので、参考程度にご覧ください。 被りやすさ4位タイ Varjo Aero&Varjo XR-3 ストラップの構造なのでしょうか?装着した後は楽なのですが、装着するのが割と大変です。 具体的に言うと、こうやって後ろから装着するとメガネがあたってしまうので、 前側を先に被る必要があります。メガネをはめ込むような感じです。 コツが必要であることと、かぶった後の位置修正のしにくさから、ちょっとメガネに優しくない HMD だなと判定させていただきました。 被りやすさ3位 Meta Quest2 Meta Quest2にはメガネ装着用アタッチメントが付属しています。 左右の差が分かるでしょうか? 左がアタッチメントを装着したQuest2です。 ばらしてみるとこんな感じで、顔にあたる部分と本体の間に噛ませるパーツがあるのです。 なので正直メガネでも快適に装着できるのですが、全体的に小ぶりなので大きめのメガネはちょっとひっかかります。ということで3位にさせていただきました。 被りやすさ2位 Vive Pro2 こちらはメガネが入る内部のスペースが大きいです。クッションが柔らかくメガネのつるが圧迫されないため、痛くなりません。 クッションの柔らかさが伝わるでしょうか? ※メガネのつるが歪んでいるのは元からです。踏みました。 被りやすさ1位 Meta Quest Pro Meta Quest Proは顔と画面の隙間が大きく、また主におでこで HMD を固定するのでメガネに干渉する部分がほとんどありません。 指さしているところをくるくる回すことで画面と顔の距離を離すことができ、鼻当てが押さえつけられることもなく快適に使用できます。ですが、光を遮断するパーツを付けるとまた変わると思います。 まとめ 今回紹介した内容を総括した図はこのようになります。 なお価格面、有線無線等は使用状況によって優劣が変わるため順位付けは行っていません。 また繰り返しになりますがメガネとの相性は個人差がありますので、あくまで筆者の場合と思っていただけると幸いです。 個人的にはメガネユーザーとしてMeta Quest Proが好みで、大貫サンタは持ち運びやすさからMeta Quest2が好みだとのことです。 どんどん新しい製品が出てくる HMD 。ARグラスの噂も色々気になるものがありますね。 新しい製品は積極的に触って調べて、お客様に適した機材やソリューションを紹介できるよう、頑張らなきゃな!と思いを新たにしたサンタたちでした。 執筆: @kano.nanami 、レビュー: @yamada.y ( Shodo で執筆されました )
アバター