GREE Engineering https://labs.gree.jp/blog Wed, 02 Jul 2025 07:04:05 +0000 ja hourly 1 https://wordpress.org/?v=6.7.2 https://labs.gree.jp/blog/wp-content/uploads/2015/01/favicon.png GREE Engineering https://labs.gree.jp/blog 32 32 81337045 Engineers' Blog Awards 2025 を開催しました! https://labs.gree.jp/blog/2025/07/24924/ Wed, 02 Jul 2025 07:04:05 +0000 https://labs.gree.jp/blog/?p=24924 こんにちは、佐島です。
昨年に引き続き、テックブログ執筆者を称える社内イベント「Engineers' Blog Awards 2025」を開催しましたので、ご紹介します。
(開催背景などは昨年のブログをご覧ください。)

選定について

Engineers' Blog Awards 2025 は、対象期間(2024年6月1日〜2025年5月31日)に投稿されたグループ全体のテックブログ記事(118本)を対象に、最も優れた記事を表彰するものです。

昨年はすべての記事を人の目で読み、一次審査を行っていましたが、今年は一次・二次審査の両方に生成AIを活用しました。
最終審査については、昨年と同様にCTOが担当しています。

表彰式

表彰式は、社内のLT大会「Engineers' Bash 2025」の中で実施されました。

ルーキー賞

ルーキー賞は、対象期間中に初めてブログを執筆した16名(17本の記事)を対象に贈られます。

ルーキー賞は「グリーエックス株式会社のテックブログ、はじめました!」を書かれた岡﨑さんが受賞されました!

大賞

大賞は、全118本の中から最も優れた1本の記事に贈られます。

今回の審査では、まず生成AIによる一次審査で候補が34本に絞り込まれました。
さらに、別の生成AIによる二次審査を経て、3本に厳選。
そして最終審査として、CTOがその中から1本を選出しました。

2025年の大賞に輝いたのは「Goのパッケージ横断でのテストカバレッジを計測して可視化した話 Now In REALITY Tech #127」を執筆されたabeさんです!

最後に

普段は、各社のテックブログが更新されるタイミングで通知を受け取り、届いた記事に一通り目を通しています。
読んだ証として、あるいは執筆者を応援するために「Like」ボタンを押した後、社内のSlackチャンネルで共有するというルーチンを、この2年ほど続けています。

外部の方にとってテックブログは、目の前の課題を解決する過程で出会ったり、転職活動の一環で企業研究のために触れたりすることが多いかもしれません。
しかし、社内の人間にとっては、リモートワークが主流となった今、その人の人となりを知るための重要なツールになっていると感じます。

このアワードを通じて、仲間が書いたアウトプットをお互いにインプットし合う文化を、改めて大切にしていきたいと強く感じました!

]]>
24924
GREE Engineers’ Bash 2025 を6月17日(火)に開催しました! https://labs.gree.jp/blog/2025/06/24914/ Wed, 25 Jun 2025 05:26:57 +0000 https://labs.gree.jp/blog/?p=24914 こんにちは、佐島です。
GREE Engineers’ Bash 2025 という社内限定イベントが昨年に引き続き開催されましたので紹介させて頂きます。
(GREE Engineers’ Bash については2018年度のブログを参照いただければと思います。)
いつもGREE Tech Conferenceをやっている東京ミッドタウン・カンファレンスにて開催しました!

LTの模様

LT のテーマは毎回同じで、最近つくってる、さわってる技術的な話とかを歓迎。
社内限定の発表につき内容についてこの場で触れることができませんのでタイトルのみで内容を妄想していただければと思います。

「LTをもっとインタラクティブに」もとい Happy Hacking のはなし(猿田 尚輝@REALITY株式会社)

PHPerコードバトルのサンプルコードを見違えるほど短くしていく(松岡 佳孝@株式会社グリー)

GDCで⚪︎⚪︎した話(西田 綾佑@株式会社WFS)

人格の与え方(松友 敦哉@株式会社WFS)

Bluff(林 記代一@株式会社グリー)

本当にあった怖い話(北國 忍@株式会社WFS)

イルカちゃんを好きになりましょう(菅原 優@グリーホールディングス株式会社)

最強のプログラムを作りたい(菊地 陽樹@株式会社WFS)

新規プロダクトにアサインされてリリースされるまでの3年半分のルック開発を5分で振り返る(礒部 直人@株式会社WFS)

CSS-like スタイリング(戛山 英高@REALITY株式会社)

アート部門

今年からアート部門のLT枠を新設し、3名の方に登壇いただきました。

「原作再現」の落とし穴!IP再現を試行錯誤した話(川﨑 未貴@株式会社WFS)

ライティング勉強会のモチベ ~ナイトレインにかけて~(中村 優美@株式会社WFS)

響け、俺の魂の『Why』!~異世界広告で『なぜ』を深掘りしてみた~(森田 豊@グリーエックス株式会社)

新卒自己紹介LT

LT発表後は今年の新卒エンジニア11名による1分間自己紹介タイムを昨年に引き続き実施しました。

投票結果

Slackにて実施した投票の結果は、以下のとおりです。

同率2位 「LTをもっとインタラクティブに」もとい Happy Hacking のはなし & 最強のプログラムを作りたい


どちらも得票数が同じだったため、同率2位となりました。

「LTをもっとインタラクティブに」もとい Happy Hacking のはなし

アプリ上で投稿した内容が登壇者の画面上に表示される仕組みを紹介する発表でした。
LT開始早々、QRコードがスクリーンに映し出され、スマホで読み取ると投稿画面へ遷移。投稿内容はリアルタイムで画面上に表示されていきます。
途中、アクセス集中により一時的に表示が止まるハプニングも発生。会場からは「負荷試験ちゃんとやろう」という声も飛び出し、意図せず二重にインタラクティブなLTとなりました。
仕組みの面白さとその場の盛り上がりが票を集めた理由と言えそうです。

最強のプログラムを作りたい

タイトルからは真面目な発表を想像しましたが、蓋を開けてみると「FXで最強の自動売買プログラムを作りたい!」という欲望に満ちた内容でした。
C#で実装されたプログラムは、過去データをもとにパラメータ調整を行い、発表の前週に本番投入。
初日から順調に利益を上げたものの、4日目にやや下落。ここで欲を抑えきれずにパラメータを調整した結果、5日目には元本割れに。
欲望に突き動かされた「最強のプログラム」が、欲によって自滅するという展開に、会場は大いに沸きました。

1位 人格の与え方


AIに話しかけても冷たい返答ばかり…そこから「AIに人格を与えたらどうなるか?」という発想でスタートした発表でした。
やがて展開は加速し、有名キャラクターの人格をAIに与え、共通のお題に答えさせる実験が始まります。
「自分なりにHello World!を出力するプログラムを書いてみて」という問いが、いつの間にか「各キャラが独自に開発した言語でHello World!を書く」方向へ進化。
世界観に忠実すぎるオリジナル言語で書かれたコードに、会場は大爆笑。
その圧倒的な独創性とエンタメ性で、文句なしの1位となりました。

最後に

今回の Engineers' Bash 2025 のロゴはアート部門の登壇者である森田さんによるデザインです。

「バッシュ」という言葉から、“ムキムキで力強いおじさん”を連想したそうで、その想像力に驚かされました。
会社の垣根だけでなく、職種の垣根も越えてイベントを共につくれたことは、大きな収穫だったと思います。

また、昨年に続き「LT大会」とは別に、Engineers' Blog Awards も同時開催しました。
その内容については、別記事にてご紹介予定です。どうぞお楽しみに。

]]>
24914
mysqld が起動の際、 innodb_buffer_pool_size に応じて buffer pool 以外で 確保しているメモリ+α https://labs.gree.jp/blog/2025/05/24866/ Tue, 20 May 2025 05:30:14 +0000 https://labs.gree.jp/blog/?p=24866 こんにちわ。せじまです。

ゴールデンウィーク中に自宅のLinuxマシンでMySQLの自由研究をやっていたとき、当初の目的は達成できなかったのですがその副産物としていろいろ気づいたことがあったので、いちおうさっくりとまとめることにしました。

公式の mysql-community-server-core 8.4.5-1ubuntu24.04 を、innodb_buffer_pool_size=40G, innodb_buffer_pool_dump_pct=0 という設定で起動したら、起動直後の時点で Resident Set Size が数GBになってました。そして、 innodb_buffer_pool_sizeを変更すると、起動直後の Residedent Set Size も変動します。

この Resident Set Size の大部分を占めているのは何なのか?という話です。

はじめに

いろいろ調べながらソースコード読んでいたところ、buf_chunk_t とかググってたら、最終的に Alibaba Cloud の Huaxiong Song さんが書かれたMySQL Memory Allocation and Management (Part II) という記事に行き着きました。こちらの 5. Summary に詳しくまとまっていますから、InnoDBにある程度くわしい人であれば、この一覧表を見ただけで「mysqld 起動直後に Resident Set Size の大部分を占めているのは何なのか」という疑問は明らかになるのかもしれません。

ちなみに Huaxiong Song さん、2025/05現時点において、MySQL 8.4のリリースノートの履歴を見る限り8.4.18.4.3にcontributeされているようで、とても優秀な方なんだろうなと思います。

あと、ついつい手癖で innodb_buffer_pool_dump_pct=0 としてしまいましたが、 innodb_buffer_pool_dump_pct の下限は10年以上前から 1 で、

innodb_buffer_pool_dump_pct=0 のように下限を下回る値は innodb_buffer_pool_dump_pct=1 扱いになり、次のようなログが出力されます。

2025-05-12T10:47:25.842731Z 0 [Warning] [MY-000081] [Server] option 'innodb-buffer-pool-dump-pct': unsigned value 0 adjusted to 1.

ただ、mysqld を停止する際に buffer pool 上にほとんどデータが読み込まれていなかったなら、 innodb_buffer_pool_dump_pct=1 としても、mysqld 再起動時に強制的に1%分のデータが必ず buffer pool 読み込まれるわけではないでしょうし、「mysqld 起動直後に Resident Set Size の大部分を占めているのは何なのか」という今回のテーマにおいて「buffer pool 以外のものを中心に調査しています」といった意図が伝わりやすくなるかなぁとも思いましたので、敢えて innodb_buffer_pool_dump_pct=0 のままでやらせていただきます。

どうやって調べたか

ちょうど WSL2 上の Ubuntu 22.04 LTS でデバッグビルドした MySQL 8.4.5 があったので、それをgdbでステップ実行しながら「この関数抜けたら Resident Set Size けっこう増えたな!」とか泥臭い確認をしてたのですが、そもそも、デバッグビルドだとリリースビルドのものより Resident Set Size はかなり多かったりします。今回、リリースビルドのバイナリでそこまで精査して試したわけではありません。

例えば、 WSL2 上の Ubuntu 24.04 LTS で公式の mysql-community-server-core と mysql-community-server-debug のメモリ使用量を比べると

$ my_print_defaults mysqld
--pid-file=/var/run/mysqld/mysqld.pid
--socket=/var/run/mysqld/mysqld.sock
--datadir=/var/lib/mysql
--log-error=/var/log/mysql/error.log
--innodb_buffer_pool_dump_pct=0
--innodb_buffer_pool_size=20G
$ dpkg -S /usr/sbin/mysqld
mysql-community-server-core: /usr/sbin/mysqld
$ sudo -u mysql /usr/sbin/mysqld &
[1] 1646
$ ps axuf | grep -e CPU -e mysqld
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root        1646  0.0  0.0  14320  6912 pts/0    S    16:11   0:00  |           \_ sudo -u mysql /usr/sbin/mysqld
root        1647  0.0  0.0  14320  1224 pts/2    Ss+  16:11   0:00  |           |   \_ sudo -u mysql /usr/sbin/mysqld
mysql       1648 28.3 14.2 24925688 2311768 pts/2 Sl  16:11   0:02  |           |       \_ /usr/sbin/mysqld
sejima      1710  0.0  0.0   3956  1928 pts/0    S+   16:11   0:00  |           \_ grep --color=auto -e CPU -e mysqld
$

$ my_print_defaults mysqld
--pid-file=/var/run/mysqld/mysqld.pid
--socket=/var/run/mysqld/mysqld.sock
--datadir=/var/lib/mysql
--log-error=/var/log/mysql/error.log
--innodb_buffer_pool_dump_pct=0
--innodb_buffer_pool_size=20G
$ dpkg -S /usr/sbin/mysqld-debug
mysql-community-server-debug: /usr/sbin/mysqld-debug
$ sudo -u mysql /usr/sbin/mysqld-debug &
[1] 1718
$ ps axuf | grep -e CPU -e mysqld
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root        1718  0.0  0.0  14312  6908 pts/0    S    16:12   0:00  |           \_ sudo -u mysql /usr/sbin/mysqld-debug
root        1719  0.0  0.0  14312  1220 pts/2    Ss+  16:12   0:00  |           |   \_ sudo -u mysql /usr/sbin/mysqld-debug
mysql       1720 29.5 19.3 27472688 3143904 pts/2 Sl  16:12   0:07  |           |       \_ /usr/sbin/mysqld-debug
sejima      1784  0.0  0.0   3956  2040 pts/0    S+   16:12   0:00  |           \_ grep --color=auto -e CPU -e mysqld
$

起動直後でこれだけ RSS が違います。ゆえに、デバッグビルドによる差分などはあるかもしれません。

予め雑なまとめ

最初にざっくりまとめておきます。

  • MySQLの起動シーケンスの中で、次のような関数でbuffer pool 以外に大量にメモリを確保するケースがあると考えられます。
  • これらのうち、 少なくとも buf_pool_register_chunk()、btr_search_sys_create()、 dict_init() は、innodb_buffer_pool_size に応じて確保されるメモリが増減します。
  • innodb_buffer_pool_size に応じて確保されるメモリの量は素数が絡んでくるので単純に試算することは難しいですが、innodb_buffer_pool_size の8~10%くらいは、追加でメモリが確保されるんじゃないかという気がします。

では詳細に入ります。

initialize_performance_schema()

これはもう改まっていうこともないですね。 Performance Schema はそれなりにメモリ食いますし設定値によって増減します。

gdb でメモリをまとめて確保してそうなところを洗い出していたらinitialize_performance_schema() も無視できてない程度にはメモリ確保してたので、いちおう挙げておこうかなくらいのところです。

例えば、公式の mysql-community-server-core 8.4.5-1ubuntu24.04 で sudo systemctl start mysql した起動直後に SHOW ENGINE PERFORMANCE_SCHEMA STATUS\G を叩くと

Type: performance_schema
  Name: performance_schema.memory
Status: 235246464

これくらいは行きますかな、まぁ P_S はそういうものなのかなと思います。

どんどん次に行きましょう。

buf_pool_register_chunk()

WL#6117 InnoDB: Resize the InnoDB Buffer Pool Online · mysql/mysql-server@0111e9c · GitHub

- 'innodb_buffer_pool_size' is changed to 'Dynamic Variable' (The Default value is still 128M. not changed.)

- able to monitor…

commit log を読むと、寡黙な紳士・木下靖文さんがMySQL5.7で実装された buffer pool のオンラインリサイズ機能に関連するところだとわかります。

WL#6117: InnoDB: Resize the InnoDB Buffer Pool OnlineのRequirementsを見ると

To optimize the resizing performance (the resizing affects to throughput, so shorter time is better), the chunk base size management is prepared also. (not needed copy whole of blocks. just add/delete chunks) The new global variable 'innodb_buffer_pool_chunk_size' is used to control the behavior.

とあります。 buffer pool は page という単位で管理されていますが、オンラインリサイズを最適化するために chunk という単位でも管理されているわけです。そしてそれは std::map で buffer pool とは異なる領域で管理されているので、 chunk が増えれば増えるほど buf_chunk_map_reg->insert() で chunk を登録する回数が増えるならば、それだけ buf_chunk_map_reg に割り当てられるヒープも拡張されうるわけですね。

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/buf/buf0buf.cc#L424-L429

/** Registers a chunk to buf_pool_chunk_map
@param[in]      chunk   chunk of buffers */
static void buf_pool_register_chunk(buf_chunk_t *chunk) {
  buf_chunk_map_reg->insert(
      buf_pool_chunk_map_t::value_type(chunk->blocks->frame, chunk));
}

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/buf/buf0buf.cc#L315-L324

/** Map of buffer pool chunks by its first frame address
This is newly made by initialization of buffer pool and buf_resize_thread.
Note: mutex protection is required when creating multiple buffer pools
in parallel. We don't use a mutex during resize because that is still single
threaded. */
typedef std::map<const byte *, buf_chunk_t *, std::less<const byte *>,
                 ut::allocator<std::pair<const byte *const, buf_chunk_t *>>>
    buf_pool_chunk_map_t;

static buf_pool_chunk_map_t *buf_chunk_map_reg;

buffer pool を chunk という単位で管理するための std::map であれば、 innodb_buffer_pool_size だけでなく innodb_buffer_pool_chunk_size によっても Resident Set Size は変動するわけです。

具体的にWSL2上で試してみましょう。環境は

$ uname --kernel-release
5.15.167.4-microsoft-standard-WSL2
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04.2 LTS
Release:        24.04
Codename:       noble
$ dpkg --list mysql-community-server
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name                   Version            Architecture Description
+++-======================-==================-============-=================================
ii  mysql-community-server 8.4.5-1ubuntu24.04 amd64        MySQL Server
$

とします。

$ my_print_defaults mysqld
--pid-file=/var/run/mysqld/mysqld.pid
--socket=/var/run/mysqld/mysqld.sock
--datadir=/var/lib/mysql
--log-error=/var/log/mysql/error.log
--innodb_buffer_pool_dump_pct=0
--innodb_buffer_pool_size=10G
$ sudo systemctl restart mysql
$ ps axuf | grep -e CPU -e mysqld
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
sejima      1854  0.0  0.0   3956  2080 pts/0    S+   19:48   0:00  |           \_ grep --color=auto -e CPU -e mysqld
mysql       1791 37.8  8.7 13502720 1415764 ?    Ssl  19:48   0:01 /usr/sbin/mysqld
$ sudo emacs /etc/mysql/mysql.conf.d/mysqld.cnf
$ my_print_defaults mysqld
--pid-file=/var/run/mysqld/mysqld.pid
--socket=/var/run/mysqld/mysqld.sock
--datadir=/var/lib/mysql
--log-error=/var/log/mysql/error.log
--innodb_buffer_pool_dump_pct=0
--innodb_buffer_pool_size=20G
$ sudo systemctl restart mysql
$ ps axuf | grep -e CPU -e mysqld
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
sejima      1965  0.0  0.0   3956  1924 pts/0    S+   19:49   0:00  |           \_ grep --color=auto -e CPU -e mysqld
mysql       1902 22.2 14.2 24729080 2315376 ?    Ssl  19:48   0:02 /usr/sbin/mysqld
$ sudo emacs /etc/mysql/mysql.conf.d/mysqld.cnf
$ my_print_defaults mysqld
--pid-file=/var/run/mysqld/mysqld.pid
--socket=/var/run/mysqld/mysqld.sock
--datadir=/var/lib/mysql
--log-error=/var/log/mysql/error.log
--innodb_buffer_pool_dump_pct=0
--innodb_buffer_pool_size=20G
--innodb_buffer_pool_chunk_size=1G
$ sudo systemctl restart mysql
$ ps axuf | grep -e CPU -e mysqld
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
sejima      2076  0.0  0.0   3956  1828 pts/0    S+   19:49   0:00  |           \_ grep --color=auto -e CPU -e mysqld
mysql       2013 55.9 13.3 24794060 2164376 ?    Ssl  19:49   0:02 /usr/sbin/mysqld
$

innodb_buffer_pool_sizeを増やすことで RSS が増えるのに対し、 innodb_buffer_pool_chunk_sizeを減らすことでRSSは減りました。

btr_search_sys_create()

これは

buf_pool_init()で buffer pool の初期化をする際、

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/buf/buf0buf.cc#L1587

btr_search_sys_create(buf_pool_get_curr_size() / sizeof(void *) / 64);

というように buffer pool のサイズに応じて

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/btr/btr0sea.cc#L186-L193

void btr_search_sys_create(ulint hash_size) {
  /* Copy the initial SYSVAR value. While the Server is starting, the updater
  for SYSVARs is not called to set their initial value. */
  btr_search_enabled = srv_btr_search_enabled;
  btr_search_sys = ut::new_withkey<btr_search_sys_t>(
      ut::make_psi_memory_key(mem_key_ahi), hash_size);
  mutex_create(LATCH_ID_AHI_ENABLED, &btr_search_enabled_mutex);
}

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/include/ut0new.h#L724-L762

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/btr/btr0sea.cc#L195-L207

btr_search_sys_t::btr_search_sys_t(size_t hash_size) {
  using part_type = btr_search_sys_t::search_part_t;
  parts = ut::make_unique_aligned<part_type[]>(
      ut::make_psi_memory_key(mem_key_ahi), alignof(part_type), btr_ahi_parts);
  static_assert(alignof(part_type) >= ut::INNODB_CACHE_LINE_SIZE);
  /* It is written only from one thread during server initialization, so it is
  safe. */
  btr_ahi_parts_fast_modulo = ut::fast_modulo_t{btr_ahi_parts};

  for (ulint i = 0; i < btr_ahi_parts; ++i) {
    parts[i].initialize(hash_size);
  }
}

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/include/ut0new.h#L2514-L2537
https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/include/ut0new.h#L1738-L1800
https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/include/ut0new.h#L1472-L1490

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/btr/btr0sea.cc#L209-L221

void btr_search_sys_t::search_part_t::initialize(size_t hash_size) {
  /* Step-1: Init latches. */
  rw_lock_create(btr_search_latch_key, &latch, LATCH_ID_BTR_SEARCH);

  /* Step-2: Allocate hash tables. */
  hash_table = ib_create((hash_size / btr_ahi_parts), LATCH_ID_HASH_TABLE_MUTEX,
                         0, MEM_HEAP_FOR_BTR_SEARCH);
  hash_table->heap->free_block_ptr = &free_block_for_heap;

#if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG
  hash_table->adaptive = true;
#endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */
}

というわけで Adaptive Hash Index のための hash table を初期化しています。

innodb_buffer_pool_size に応じて btr_search_sys_create() に渡される hash_size が増減することを、先ほどのWSL2の環境で試してみましょう。
sudo apt install bpftrace しつつ mysql-community-server-core-dbgsym もインストールして

$ dpkg --list mysql-community-server-core-dbgsym
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name                               Version            Architecture Description
+++-==================================-==================-============-=============================================
ii  mysql-community-server-core-dbgsym 8.4.5-1ubuntu24.04 amd64        debug symbols for mysql-community-server-core
$

$ sudo bpftrace -l 'uprobe:/usr/sbin/mysqld:*btr_search_sys_create*'
uprobe:/usr/sbin/mysqld:_Z21btr_search_sys_createm
uprobe:/usr/sbin/mysqld:_Z21btr_search_sys_createm.cold
$ sudo bpftrace -e 'uprobe:/usr/sbin/mysqld:_Z21btr_search_sys_createm* {printf("%d\n", arg0);}'
Attaching 2 probes...

して別のターミナルでエディタで設定書き換えつつ

$ my_print_defaults mysqld
--pid-file=/var/run/mysqld/mysqld.pid
--socket=/var/run/mysqld/mysqld.sock
--datadir=/var/lib/mysql
--log-error=/var/log/mysql/error.log
--innodb_buffer_pool_dump_pct=0
--innodb_buffer_pool_size=10G
$ sudo systemctl restart mysql
$ sudo emacs /etc/mysql/mysql.conf.d/mysqld.cnf
$ my_print_defaults mysqld
--pid-file=/var/run/mysqld/mysqld.pid
--socket=/var/run/mysqld/mysqld.sock
--datadir=/var/lib/mysql
--log-error=/var/log/mysql/error.log
--innodb_buffer_pool_dump_pct=0
--innodb_buffer_pool_size=20G
$ sudo systemctl restart mysql

とすると、さきほど bpftrace したターミナルでは

$ sudo bpftrace -e 'uprobe:/usr/sbin/mysqld:_Z21btr_search_sys_createm* {printf("%d\n", arg0);}'
Attaching 2 probes...
20971520
41943040

といったように、 innodb_buffer_pool_size に応じて btr_search_sys_create() で指定される hash_size も変わりそうだと確認できます。

ただ、「Adaptive Hash Index は MySQL 8.4 だとデフォルトで無効化されたのでは?なぜ Adaptive Hash Index のための hash table が起動時に初期化されている?」と思うかもしれませんが、 8.4 でも動的に有効化できるので、起動時にAdaptive Hash Indexが無効化されていても、そのための hash table の領域は確保しておく方が無難なんでしょうね(たぶん)。

dict_init()

dict_init() 内で innodb_buffer_pool_size に応じて変化する要素のうち、サイズが大きくなるところとしては、 MySQL8.0.27や8.0.28あたりのmemory/innodb/hash0hashやut0new.hなどの話 で取り上げた dict_sys->table_hash や dict_sys->table_id_hash です。

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/dict/dict0dict.cc#L1021-L1025

dict_sys->table_hash = ut::new_<hash_table_t>(
      buf_pool_get_curr_size() / (DICT_POOL_PER_TABLE_HASH * UNIV_WORD_SIZE));

  dict_sys->table_id_hash = ut::new_<hash_table_t>(
      buf_pool_get_curr_size() / (DICT_POOL_PER_TABLE_HASH * UNIV_WORD_SIZE));

改めて MySQL8.0.27や8.0.28あたりのmemory/innodb/hash0hashやut0new.hなどの話 を読み返していて「あーここ間違ってたなー」「でも、都度都度malloc()してるところが完全にないわけじゃないんだよなー」と反省したところがあったので、訂正させていただきますと、例えば

dict_sys->table_hash = ut::new_<hash_table_t>(
      buf_pool_get_curr_size() / (DICT_POOL_PER_TABLE_HASH * UNIV_WORD_SIZE));

ut::new_<hash_table_t>() すると

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/include/hash0hash.h#L376-L385

/* The hash table structure */
class hash_table_t {
 public:
  hash_table_t(size_t n) {
    const auto prime = ut::find_prime(n);
    cells = ut::make_unique<hash_cell_t[]>(prime);
    set_n_cells(prime);


    /* Initialize the cell array */
    hash_table_clear(this);

で hash_table_clear() しているので、

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/include/hash0hash.ic#L44-L51

/** Clears a hash table so that all the cells become empty. */
static inline void hash_table_clear(
    hash_table_t *table) /*!< in/out: hash table */
{
  ut_ad(table);
  ut_ad(table->magic_n == hash_table_t::HASH_TABLE_MAGIC_N);
  memset(table->cells.get(), 0x0, table->get_n_cells() * sizeof(hash_cell_t));
}

で memset() で 0x0 を設定しているので、ここで table->cells は page fault 起きて実際にメモリが割り当てられてると思います。table->cells の定義は

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/include/hash0hash.h#L450-L455

/** The pointer to the array of cells.
  If type==HASH_TABLE_SYNC_RW_LOCK it is:
  - modified when holding X-latches on all n_sync_obj
  - read when holding an S-latch for at least one n_sync_obj
  */
  ut::unique_ptr<hash_cell_t[]> cells;

こちらで、

都度都度メモリが割り当てられるのは、

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/include/hash0hash.h#L61-L63

struct hash_cell_t {
  void *node; /*!< hash chain node, NULL if none */
};

こっちでした。

つまるところ、 mysqld 起動時に dict_sys->table_id_hash->cells が初期化されてそれらにはヒープで実メモリが割り当てられますが、それぞれの cells にぶら下がる node については、実際に使うときにメモリが割り当てられるのだと私は認識しています。

innodb_buffer_pool_size を変更しながら起動時の Resident Set Size を確認

ではもう一度、 innodb_buffer_pool_size を1G, 2G, 10G, 20G で、mysqld 起動直後の Resident Set Size を比較してみましょう。

$ my_print_defaults mysqld
--pid-file=/var/run/mysqld/mysqld.pid
--socket=/var/run/mysqld/mysqld.sock
--datadir=/var/lib/mysql
--log-error=/var/log/mysql/error.log
--innodb_buffer_pool_dump_pct=0
--innodb_buffer_pool_size=1G
$ sudo systemctl restart mysql
$ ps axuf | grep -e CPU -e mysqld
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
sejima      3400  0.0  0.0   3956  1932 pts/0    S+   21:21   0:00  |           \_ grep --color=auto -e CPU -e mysqld
mysql       3339 25.0  3.7 3236412 600680 ?      Ssl  21:21   0:00 /usr/sbin/mysqld
$ sudo emacs /etc/mysql/mysql.conf.d/mysqld.cnf
$ my_print_defaults mysqld
--pid-file=/var/run/mysqld/mysqld.pid
--socket=/var/run/mysqld/mysqld.sock
--datadir=/var/lib/mysql
--log-error=/var/log/mysql/error.log
--innodb_buffer_pool_dump_pct=0
--innodb_buffer_pool_size=2G
$ sudo systemctl restart mysql
$ ps axuf | grep -e CPU -e mysqld
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
sejima      3511  0.0  0.0   3956  1916 pts/0    S+   21:22   0:00  |           \_ grep --color=auto -e CPU -e mysqld
mysql       3448 33.3  4.3 4873688 707752 ?      Ssl  21:21   0:00 /usr/sbin/mysqld
$ sudo emacs /etc/mysql/mysql.conf.d/mysqld.cnf
$ my_print_defaults mysqld
--pid-file=/var/run/mysqld/mysqld.pid
--socket=/var/run/mysqld/mysqld.sock
--datadir=/var/lib/mysql
--log-error=/var/log/mysql/error.log
--innodb_buffer_pool_dump_pct=0
--innodb_buffer_pool_size=10G
$ sudo systemctl restart mysql
$ ps axuf | grep -e CPU -e mysqld
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
sejima      3622  0.0  0.0   3956  1988 pts/0    S+   21:22   0:00  |           \_ grep --color=auto -e CPU -e mysqld
mysql       3558 65.8  8.7 13175040 1412272 ?    Ssl  21:22   0:01 /usr/sbin/mysqld
$ sudo emacs /etc/mysql/mysql.conf.d/mysqld.cnf
$ my_print_defaults mysqld
--pid-file=/var/run/mysqld/mysqld.pid
--socket=/var/run/mysqld/mysqld.sock
--datadir=/var/lib/mysql
--log-error=/var/log/mysql/error.log
--innodb_buffer_pool_dump_pct=0
--innodb_buffer_pool_size=20G
$ sudo systemctl restart mysql
$ ps axuf | grep -e CPU -e mysqld
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
sejima      3733  0.0  0.0   3956  1916 pts/0    S+   21:22   0:00  |           \_ grep --color=auto -e CPU -e mysqld
mysql       3669 60.0 14.2 24794616 2314380 ?    Ssl  21:22   0:02 /usr/sbin/mysqld
$

表にまとめると次のようになります。

innodb_buffer_pool_size Resident Set Size(KiB)
1G 600680
2G 707752
10G 1412272
20G 2314380

innodb_buffer_pool_size が1G のときと2GBのときの Resident Set Size の差分は 707752KiB - 600680KiB = 約104.5MiBくらいですが、10Gと20Gのときの差分は 2314380KiB - 1412272KiB = 約880.9MiBにもなりました。dict_sys->table_hash など hash table の初期化は

https://github.com/mysql/mysql-server/blob/mysql-8.4.5/storage/innobase/include/hash0hash.h#L379-L381

hash_table_t(size_t n) {
    const auto prime = ut::find_prime(n);
    cells = ut::make_unique<hash_cell_t[]>(prime);

というように素数が絡んでくるので単純に試算することは難しいですが、 innodb_buffer_pool_size の+8~10%くらいは、 buffer pool 以外の領域のために、追加でヒープが割り当てられると見ておいて良さそうな気がします。

mysqld 起動直後で buffer pool で使用済みの領域もいちおう確認

いちおう、 mysqld 起動直後に buffer pool で使用済みの領域がどれくらいあるかも確認しておきましょう。

MySQLをクリーンインストールした状態で innodb_buffer_pool_dump_pct=0 にして innodb_buffer_pool_size=1G, 20Gで比較してみると、せいぜい 15~17MB弱程度だとわかります。

$ my_print_defaults mysqld
--pid-file=/var/run/mysqld/mysqld.pid
--socket=/var/run/mysqld/mysqld.sock
--datadir=/var/lib/mysql
--log-error=/var/log/mysql/error.log
--innodb_buffer_pool_dump_pct=0
--innodb_buffer_pool_size=1G
$ sudo systemctl restart mysql
$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.4.5 MySQL Community Server - GPL

Copyright (c) 2000, 2025, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> select @@innodb_buffer_pool_size, @@innodb_page_size, @@innodb_buffer_pool_size/@@innodb_page_size;
+---------------------------+--------------------+----------------------------------------------+
| @@innodb_buffer_pool_size | @@innodb_page_size | @@innodb_buffer_pool_size/@@innodb_page_size |
+---------------------------+--------------------+----------------------------------------------+
|                1073741824 |              16384 |                                   65536.0000 |
+---------------------------+--------------------+----------------------------------------------+
1 row in set (0.00 sec)

mysql> show global status like 'innodb_buffer_pool_pages_free';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| Innodb_buffer_pool_pages_free | 64573 |
+-------------------------------+-------+
1 row in set (0.00 sec)

mysql> select ((@@innodb_buffer_pool_size/@@innodb_page_size)-64573)*@@innodb_page_size/1024.0/1024.0;
+-----------------------------------------------------------------------------------------+
| ((@@innodb_buffer_pool_size/@@innodb_page_size)-64573)*@@innodb_page_size/1024.0/1024.0 |
+-----------------------------------------------------------------------------------------+
|                                                                         15.046875000000 |
+-----------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> exit
Bye
$ sudo emacs /etc/mysql/mysql.conf.d/mysqld.cnf
$ my_print_defaults mysqld
--pid-file=/var/run/mysqld/mysqld.pid
--socket=/var/run/mysqld/mysqld.sock
--datadir=/var/lib/mysql
--log-error=/var/log/mysql/error.log
--innodb_buffer_pool_dump_pct=0
--innodb_buffer_pool_size=20G
$ sudo systemctl restart mysql
$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.4.5 MySQL Community Server - GPL

Copyright (c) 2000, 2025, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> select @@innodb_buffer_pool_size, @@innodb_page_size, @@innodb_buffer_pool_size/@@innodb_page_size;
+---------------------------+--------------------+----------------------------------------------+
| @@innodb_buffer_pool_size | @@innodb_page_size | @@innodb_buffer_pool_size/@@innodb_page_size |
+---------------------------+--------------------+----------------------------------------------+
|               21474836480 |              16384 |                                 1310720.0000 |
+---------------------------+--------------------+----------------------------------------------+
1 row in set (0.00 sec)

mysql> show global status like 'innodb_buffer_pool_pages_free';
+-------------------------------+---------+
| Variable_name                 | Value   |
+-------------------------------+---------+
| Innodb_buffer_pool_pages_free | 1309647 |
+-------------------------------+---------+
1 row in set (0.00 sec)

mysql> select ((@@innodb_buffer_pool_size/@@innodb_page_size)-1309647)*@@innodb_page_size/1024.0/1024.0;
+-------------------------------------------------------------------------------------------+
| ((@@innodb_buffer_pool_size/@@innodb_page_size)-1309647)*@@innodb_page_size/1024.0/1024.0 |
+-------------------------------------------------------------------------------------------+
|                                                                           16.765625000000 |
+-------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql>

システムデータベースに含まれるアカウント情報や権限情報がロードされることなども考えると、 0 にはならないでしょうがそう多いものでもないとわかります。initialize_performance_schema()、buf_pool_register_chunk()、btr_search_sys_create()、 dict_init() などの関数内で確保されるメモリと比較すれば、わずかなものと言えるでしょう。

おわりに

私が本来検証したかったのは、

  • mysqld は、たくさんテーブルを開くと buffer pool 以外に大量にメモリを確保することが経験上わかっている

という経験則に対して

  • dict_sys->table_hash->cells にnode を割り当てるとして、テーブルのメタデータにはテーブル名やカラム名が含まれるのだから、これらがglibc mallocなどで割り当てられるなら arena に割り当てられるのでは
  • 例えば CREATE TABLE というDDLを実行した場合、 CREATE TABLE の STATEMENT やその Result Set と、テーブルのメタデータに含まれるテーブル名やカラム名が同じ arena に割り当てられた場合、Result Set を返せば STATEMENT や Result Set は free() で arena から開放できるけど、メタデータのテーブル名やカラム名が dict_sys->table_hash や dict_sys->table_id_hash などでキャッシュされ続けるなら、 arena はフラグメントしていくのでは?

という仮説を立証できるテストケースを作れるか、ということだったんですが、いろいろ試しているうちに mysqld 起動直後の Resitent Set Size が気になってしまったので、そちらを調べていたらゴールデンウィークが終わってしまいました。

glibc malloc でなく tcmalloc や jemalloc を使うようにしていろいろ試しながら pmap -x を見てたら思うところなどあったので、それについてはまたそのうち blog に書けたら良いかなと思うなどしています。

References

]]>
24866
SREcon25 Americasに参加してきました https://labs.gree.jp/blog/2025/05/24848/ Tue, 13 May 2025 07:07:36 +0000 https://labs.gree.jp/blog/?p=24848 インフラストラクチャ部の岩堀(@egmc)です。

3/25-27にアメリカのサンタ・クララで開催されたSREcon25Americasに参加してきましたので、こちらの記事では現地の様子やおすすめセッションの紹介します。

なお、ほとんどのセッションについてはオープンアクセスポリシーによりすでにスライド、動画が公開されています。本記事からセッションに興味を持たれた方はぜひリンク先もチェックしてみてください。

SREconについて

既にSREというロールで業務に当たられている方にとっては説明不要かと思いますが、USENIXによって開催されているベンダー中立なSREの国際カンファレンスです。

2023年を最後にAPACでの開催がなくなり、現在は概ね1年に1回Americas、Europe/Middle East/Africaそれぞれで開催されています。

なぜ今回参加したのか

SREconへの参加は2019年以来2回目となります。

今回このタイミングで参加することにした理由のひとつとして、コンテンツの変化がありました。

SREcon25ではAmericasで初となるディスカッショントラックの導入に加え、Birds-of-a-Feather Sessions (BoFs)もアナウンスされていました。

SREconは先に述べた通りセッションの動画自体は概ね公開されます。

しかしディスカッションではそのトピックのエキスパートがリードし、参加者が自由に発言してインタラクティブにやりとりすることができます。これは現地ならではの体験です。

これらを主な目的として、今回6年ぶりにSREconへの現地参加をさせて頂きました。

会場のハイアット

会場のハイアット

ディスカッションについて

以下のディスカッショントラックに参加しました。

  • What Do SRE ICs Do? How to Build SRE Skillsets
  • Service Level Objectives
  • Tech Debt
  • Observability
  • Open Unconference on SRE

開催形式は主にブレークアウトディスカッション(テーブルごとに分かれてグループディスカッションする)とAMA(Ask Me Anything、セッションオーナーが話題を振って参加者全体で議論する)に分かれます。

どちらも使うものは付箋とペンくらいで、かなりアナログに進行していきます。

正直AMAは特に物理的な距離の遠さなども相まって英語が聞き取りづらくややむずかしいな、という感じでしたが、ブレークアウトディスカッションについては比較的狭いコンテキストの中で話し合われるのでなんとか参加できました。

特にObservabilityのディスカッションで、Observabilityにかかるコストについての責任をSREが持つべきか?等、それぞれの(だいたいにおいて大きなスケールをもつ)組織の悩みポイントについて話し合われていたのが印象的でした。

ディスカッショントラックでのテーマ決め

ディスカッショントラックでのテーマ決め

BoFのテーマを書くボード

BoFのテーマを書くボード

 

セッションの紹介

参加したものの中から3つピックアップして紹介します。

Safe Evaluation and Rollout of AI Models

https://www.usenix.org/conference/srecon25americas/presentation/burns

MicrosoftのCopilotの事例として、AIモデルをリリースする際に行っていることを紹介するセッションです。

会場ではその場で参加者にアンケートを取っていましたが、AIをプロダクションで利用している人は15%くらいでした。

通常SLOなどに使われる指標(レイテンシなど)に加え、AIチャットを提供するサービスとして品質を評価する必要があり、ユーザーが回答に満足したかどうか?yes/noをアンケートを取って指標とするが、回答に満足した人は通常yes(Thumbs Up)はしないので主にno(Thumbs down)をみるなど品質測定の難しさなどが話されていました。

データ収集のアプローチとしては、プライバシー上の理由により、入力されたコンテキストをデフォルトでは収集しないようにしているがそれではモデル改善のための十分なデータが得られず、そのためMicrosoft内部のユーザー(従業員)からデータを集めるという話が特徴的でした。企業として十分に大きな規模があるからこそ取れる戦略ですね。

Tackling Slow Queries: A Practical Approach to Prevention and Correction

https://www.usenix.org/conference/srecon25americas/presentation/famili

Shopifyのエンジニアによるセッションです。

ShopifyではTimeout / Circuit Breaker等複数の仕組みによりDBはresilientに構成されているが、Slow Queryによる問題は依然としてあり、それに対処する話がメインでした。

具体的にはActive Recordのコード変更検知を行い、テストでみつかった新しいクエリのexplainを自動で実行、Full Table ScanがみつかったらWarningを出して修正を促すといった手法が提示されました。

講演の様子

講演の様子

Distributed Tracing in Action: Our Journey with OpenTelemetry

https://www.usenix.org/conference/srecon25americas/presentation/detsicas

Cisco ThousandEyesのエンジニアによるセッションです。

OpenTelemetryによる計装を導入していく王道のセッションという内容で、これから組織として導入していくフェーズの方などに良い内容だと思います。

計装に関するテクニカルな話の他に新規サービスでは導入することを義務化して、Jenkinsへの導入など実例で効果を示すことで導入しやすくする、といった組織への導入のヒントも話されています。

なお、Cisco ThousandEyes自体もネットーワーク監視のソリューションですがトレースのバックエンドとしてはGrafana Tempoが使われていました。
理由としてはクエリ言語が柔軟なところと、メトリクスも使ってたので合わせて使うことにしたそうです。

おわりに

以上、簡単ですがSREcon25 Americasへの参加レポートでした。
全体としてはやはりAI・機械学習セッションが増えている傾向はありつつ、かといってAI一色ではなくLinuxシステム上のメモリカウントの話だけをするセッションやVarnishのような歴史のあるプロダクトとOtelの組み合わせといったセッションもあり、SREという領域の広さを改めて感じるイベントでした。

期待していたディスカッショントラック、BoFは英語でのコミュニケーションの難しさはありつつ、関連本の著者やその領域の専門家が直接フォローしてくれるのでとても体験として良いものでした。

開催場所や言語の壁もありなかなか気軽に参加できるイベントではないですが、いずれまた機会があれば参加してみたいですね。

もしこちらの記事で少しでも興味を持って頂けましたら、まずはプログラムを眺め、公開されている動画などをチェックして頂ければ幸いです。

]]>
24848
GREE Tech Conference 2024 謎解きイベント After Partyを完成させて! 解説 https://labs.gree.jp/blog/2025/01/24655/ Fri, 17 Jan 2025 09:10:32 +0000 https://labs.gree.jp/blog/?p=24655 皆さん、こんにちは!
謎解き作成者の日高です。普段はインフラ部のエンジニアとして働いています。

本記事では、2024年10月25日(金)に実施した、GREE Tech Conference 2024の謎解きイベントを解説します。

参加いただいた方は、分からなかった問題や自身の答えとの答え合わせとして読んでくださると幸いです。
また、参加できなかったという方向けにも謎解きを楽しめるように記事を書いています。興味のある方は挑戦してみてください。

謎解きで使用したアイテム

今回の謎解きでは、以下のアイテムを使用して謎を解いていきます。

入場時に受付で配布されたもの

会場内に設置されたもの

謎解きの過程でスタッフから見せてもらえるヒントアイテム

※ 謎を解く場合、次のヒントアイテムは指示されたタイミングで確認してください。

以上が、謎解きで使用したアイテムになります。

それではアイテム1:冊子に記された謎を解いていきましょう!

謎1

YouTubeにアップロードされているオープニングを見てみましょう。

オープニングでのアナウンス通り、それぞれ以下が回答になります。

  1. ×:「小さな」ではなく、「盛大な」拍手のため不正解
  2. ◯:記載通りのため正解
  3. ◯:記載通りのため正解

謎2

議事録を読むと、アイテム1:冊子の後ろにあるヒント(去年のマップ)とアイテム3:今年のマップを比較すれば良いことに気がつきますね。

比較した結果、「トイレ」と「はじまりの部屋」が増えていることが分かるため、以下が回答になります。

謎3

ヒントと合致する表示はアイテム3:今年のマップから見つけることができます。

謎4

計算式の左辺のアイコンが出てくるものを探してみましょう。

予定表が見つかるはずです。
予定表と計算式を比較すると以下の法則性を見つけることができますね。

法則性を踏まえて、右辺は予定表の曜日をカレンダーの色で表していることに気がつきましたか?
このため、答えは「赤色のもの」となります。

※ この謎では、カレンダーが平日は黒、土曜日は青、日曜日は赤で書かれていることを前提としています。

謎5

変換元のマークはアイテム3:今年のマップにあります。
そして、次のような予測もできそうなので、実際に確かめてみましょう。

マークの個数も調べてみると、アルファベットの可能性が高そうです。
実際に変換してみましょう。

予想通り記号の数がアルファベットの順番になっているようですね!
同様に答えも導けるかと思います。

謎6

問題文の下に書かれているヒントに注目しましょう。
変換後のマークはそれぞれ以下の表に載っていて、五十音を示していますね。

しかし、問題本文の指示文の意図が分かりません。
実は、ヒントの文字列である「受付」「右」「モニター」が指す場所では映像が流れています。
ヒントとアイテム4:受付の右手にあるモニターに映し出された映像のマークを用いて変換してみましょう。
映像の内容はこちら(再掲)に載せています。

変換してみましたが、回答になりえそうな文字列はありません。
五十音表のヒントにある注意文も読んでみましょう。

実は会場に設置されたアイテム5:たくさんのマークにはマグネットがくっつくものと、くっつかないものがあったのです。
くっつくかくっつかないかを見分けるためにはアイテム3:マグネットを持って会場を歩き回る必要がありました。

マグネットがくっつくマークの文字列を読んでみると次の回答が導けます。

謎7

この謎は、今までの謎を解いて初めて挑戦できる問題になっています。
今までの謎の答えを当てはめてみましょう。

指示通り、「キーワード」を求めていきます。
「旗」→「動物」→「赤色のもの」→「食べ物」→「ロゴ」を通る最短経路を探してみましょう。

※本来は会場を歩き回って最短経路を探さないといけないのですが、かなり大変なので「ヒントアイテム3:謎7のお助けアイテム」として「マークが記された地図」を配布しました。(ただし謎解きとは別に会場で行われていたスナックラリーを制覇するという条件を満たす必要があります)

次の図は、回答になりうる経路の候補です。

最短経路は、赤色の経路になりそうですね。
謎6と同様の方法で文字を読んでみましょう。
「きいろのな」つまり「黄色の名」が答えになります。

「黄色の名」と言えば、予定表にある「ナカムラ」が思いつきますね。

問題文を読み直してみましょう。

GREE Tech Conference 2024のテーマは「Hackpackers」でした。
指示通り、ピンク色のTシャツを着たスタッフに「Hackpackersをナカムラに伝えて」と依頼してみましょう。
スタッフから紙を見せてもらえます。

謎8

見せられた紙は以下の通りです。ヒントアイテム1:キーワードがナカムラの場合

書いてある通り、宝箱の横にあるQRコードに上記の4桁の数字を入力でクリア...ですが、ここで2つの違和感に気がつきませんか?

違和感1:謎8と書かれている

違和感2:制作責任者がナカムラ

最後の謎がこんな簡単な指示文だけ?
謎を作ったのはナカムラではなく、初代ご褒美担当の「ワタナベ」では?
しかもナカムラはパスワードを知らないはずです。では、なぜこのパスワードを用意できたのでしょうか...?

ということで、この違和感に気がつくことが謎8のスタートになります。
最初に、ナカムラはそもそも「謎が解けなかった」と言っていたはずですね。
どこかに見落としがありそうです。
見落としがないか、ワタナベとナカムラの行動を辿ってみましょう。

議事録

  • ワタナベは去年のマップを参考にして、色々と進めていくと言っている。
  • ナカムラの追記より、はじまりの部屋が使えることが決まったのは、10/8である。

予定表

  • 9/21(土)にワタナベはご褒美の準備を完了している。

あらすじ

  • ワタナベはご褒美の入った箱を開けるパスワードを忘れたときのために「謎」を残している。

時系列を整理してみると以下の通りになります。

  • 5/20(月):ワタナベは去年のマップを参考にして、色々と進めていくと言っている
  • 9/21(土):ワタナベがご褒美の準備を完了(と言うことは謎解きの用意も完了)
  • 10/8(火):会場を広げることが決定

これらのことから、ワタナベは去年のマップをベースに謎を作っていたことが分かります。

去年のマップをベースとした場合、謎7におかしな点がありますね。

ワタナベが使っていたマップは今年のマップではなく去年のマップです。

去年は右下の部屋を使っていないことがわかっています。

そのため、最短経路は青色のルートになりますね。

では、もう一度解き直してみましょう。

本当の答えは以下になります。

ピンク色のTシャツを着たスタッフに「Hackpackersをワタナベに伝えて」と依頼します。
すると、スタッフから以下の紙を見せてもらえます。(ヒントアイテム2:キーワードがワタナベの場合)

最終的な回答は、After Partyの開始時間を24時間表記にした「1845」というわけです。
また、紙には制作責任者「ワタナベ」とあるので、本当の正解であることが分かりますね。

謎を解くことができなかったナカムラは、あろうことか箱のパスワードを捏造していたのでした。
ちなみにイベント開催前に実施されたオフィスツアーに参加した人は、謎7が解けず捏造を決意した際にナカムラが残したメモ(と言う名のヒント)を見ることができたみたいです。

宝箱の横にあるQRコードに1845と入力することで無事に開けることができました!

後書き

ここまでお付き合いいただき、ありがとうございました。

謎解きイベントはfreeeさんの技術カンファレンスである「freee技術の日」で実施されていたものを参考に実施させていただきました。freeeさんの謎解きも非常に面白いものになっていましたので、ぜひ挑戦してみてください。(謎解き記事

また、当日参加いただいた皆さん、本当にありがとうございました。予想を遥かに上回る方に挑戦してもらえて、非常に嬉しかったです。
一方、難易度を上げ過ぎた、想定外のミスがあったなどの反省点もありましたので、次回以降は改善していきたいです。

以上、GREE Tech Conference 2024で実施した謎解きイベントの解説でした!

]]>
24655
WebAssembly Validationアルゴリズムの学び方 https://labs.gree.jp/blog/2024/12/24431/ Tue, 03 Dec 2024 21:30:00 +0000 https://labs.gree.jp/blog/?p=24431 どーもちょびえです。余暇でUnity向けにWebAssembly Specification 2.0に準拠したWebAssemblyのRuntimeとWGSL派生言語からWasmへコンパイルする機能を開発しています。

この記事はWebAssembly / Wasm Advent Calendar 2024の4日目で、WebAssembly Validationアルゴリズムの学び方について仕様書をもとに具体的な手法を解説します。

対象の読者

本記事ではWebAssemblyのデザインゴールの確認から、平易に書かれたHTML+JavaScriptで実装された簡易WebAssemblyインタプリタで動作するサンプルを使ってValidationと制御構造の問題に取り組みます。そのため初学者は仕様を理解していないと難解な部分もあります。もし難しいと感じた場合はstep by stepで説明されている「RustでWasm Runtimeを実装する」を読むことを推奨します。

また、WebAssembly Specification 2.0 (Draft)をお供に解説を行います。WebAssemblyの仕様書は世間的に難解であると評価されることがしばしばある仕様書です。とはいえ、RFCを元になにかしらの実装をした経験がある筆者としては、記述は難解な部分もあるけれど親切で明快な仕様書という認識です。

現代では仕様書を読んでよくわからない部分があったとしても、ChatGPTにコピペして解説してください!と質問すれば大抵いい感じに解説してくれるので身構えなくても大丈夫ですよ!


補足

私はWebAssemblyの仕様書pdfをChatGPTにuploadして使っています。2024年12月現在のChatGPTよくある問題としては、何もコンテキストを与えずに質問するとWebAssembly 1.0の仕様で返してくることが多いので注意が必要です。

なお、生成AIを業務で利用する際は所属企業のルールに準じてください。

WebAssemblyが目指すデザインゴール

まずはWebAssemblyのDesign Goalsの確認をします。このゴールを読むことでなぜValidationが必要なのか分かります。

英文だと分かりづらいので日本語に翻訳して引用します。原文をChatGPTにコピー&ペーストして「次の英語を日本語に翻訳してください」でおおよそ正確な日本語訳を出力してくれます。もちろんそのまま鵜呑みにせずに自身の基礎的な英語力でニュアンスがあっているかを再検証する力は必要です。

WebAssemblyの設計目標は以下の通りです:

  • Fast, safe, and portable semantics:

    • Fast: ほぼネイティブコードのパフォーマンスで実行され、現代のすべてのハードウェアに共通する機能を活用します。
    • Safe: コードは検証され、メモリ安全な、サンドボックス化された環境で実行され、データ破損やセキュリティ侵害を防ぎます。
    • Well-defined: 有効なプログラムとその挙動を完全かつ正確に定義し、非公式および公式の両方で容易に理由付けできるようにします。
    • Hardware-independent: すべての現代的なアーキテクチャ、デスクトップやモバイルデバイス、組み込みシステム上でコンパイル可能です。
    • Language-independent: 特定の言語、プログラミングモデル、オブジェクトモデルを優遇しません。
    • Platform-independent: ブラウザに組み込まれたり、スタンドアロンのVMとして実行されたり、他の環境に統合されたりできます。
    • Open: プログラムがシンプルかつ普遍的な方法で環境と相互運用可能です。
  • Efficient and portable representation:

    • Compact: テキストやネイティブコード形式よりも小さく、転送が速いバイナリ形式を持ちます。
    • Modular: プログラムを小さな部分に分割し、別々に転送、キャッシュ、利用することができます。
    • Efficient: Just-in-time (JIT) または Ahead-of-time (AOT) コンパイルのどちらでも、単一の高速なパスでデコード、検証、コンパイルが可能です。
    • Streamable: データ全体を受け取る前に、可能な限り早くデコード、検証、コンパイルを開始できます。
    • Parallelizable: デコード、検証、コンパイルを多数の独立した並列タスクに分割可能です。
    • Portable: 現代のハードウェアで広くサポートされるアーキテクチャ以外の仮定をしません。

WebAssemblyのデザインゴールは、現代のハードウェア上で高速かつ安全に動作し、移植性と空間効率に優れた汎用的なプログラムの実行環境を提供し続けることだと言えます。これらの項目はWebAssemblyの性能や安全性や移植性を支える重要な指針で、実現する為には各モジュールが期待通りに動作することを保証するなにかしらの仕組みが必要となります。

WebAssemblyにおけるValidationの位置づけ

Validationという言葉を聞いても、具体的に何をするのかがすぐにはイメージしづらいかもしれません。WebAssembly関連の用語については、IntroductionのOverviewで説明されており、その中でSemantic PhasesとしてWebAssemblyのフェーズが定義されています。ここでは簡潔に要点をまとめます。

  • Decoding: バイナリ形式のデータを内部的な表現に変換する手続き
  • Validation: デコードされたデータが正しい形式かどうかを確認し、型チェックなどを行う手続き
  • Execution: 実際にモジュールを実行する手続き。インスタンス化と関数呼び出しの2つに分けられる

ざっくりいうとWebAssemblyのValidationはJavaのbytecode verifierのような位置づけとなります。もしくは、WebAPIを扱った経験がある方であればFormのValidationと似たものと考えると分かりやすいかもしれません。こう考えると必要性が判断しやすいと思います。

Runtime実装勢からみるValidation

趣味から始まっているようなRuntime実装勢的にはValidationはどうしても省かれがちです。命令の数だけ検証コードを書いていくため単純に実装量が増えてしまうからです。たしかに省略しようとする気持ちも分かります。しかし、実装することで「よくわからないバグのような挙動との遭遇確率を減らすことが出来る」という超特大のメリットがあります。

私自身、自作言語から出力したWebAssemblyバイナリを自作Runtimeで動かしているときに、しばしばバグのような挙動に悩まされました。その原因を調査してみるとWebAssemblyの仕様に反したバイナリを実行していたことが問題というケースが多くありました。

もしValidationを正確に実装していれば、検証エラーとしてすぐに問題の切り分けができたので時間が節約できたはずです。

Validationの実装方法を考える

WebAssemblyの検証はどうすればいいのか、設計・実装に悩む部分もたしかにあります。そんな私達のためにWebAssembly SpecificationのAppendixに7.3 Validation Algorithmというそのものズバリなセクションがあります。この一連のセクションでは擬似コードを通してValidationアルゴリズムを解説しています。この通りに設計して考えながら実装していけば大まかに仕様に準拠した動作をするRuntimeとValidationの設計ができてしまいます。なんて優しいんだ!英文は毎度のごとくChatGPTに翻訳してもらいつつ読み進めてみてください。

7.3 Validation Algorithmの序文の翻訳文を引用します。

WebAssemblyのバリデーション仕様は純粋に宣言的です。モジュールや命令列が有効であるために満たすべき制約が記述されています。

このセクションでは、コード、つまり命令列を効果的にバリデーションするための、健全かつ完全なアルゴリズムの骨組みを概略的に説明します。(バリデーションの他の側面は実装が比較的容易です。)
実際、このアルゴリズムはバイナリ形式で出現する命令コードの平坦な列に対して表現され、これを単一のパスで処理します。そのため、デコーダに直接統合することが可能です。
アルゴリズムは型付きの擬似コードで表現されており、その意味は自明であることを意図しています。

と説明されています。

自分でRuntimeをどう実現するか考えるのも楽しいのですが、WebAssemblyの制御構造を満たす設計をするのは少し難易度が高めです。例えば、WebAssemblyのジャンプ系では移動先がラベルで指定されており、次に移動すべき場所の絶対的な位置がわかりません。これはセキュアな実行をするためでもあります。またbr命令で制御構造の途中で抜けることもよくあります。当然のように制御構造はネストしていくため、正確に情報をトラッキングしないと正しく動作しません。WebAssemblyの制御構造にまつわる仕様をすべて抑えておかないと大幅に書き直す羽目になります。

Runtimeを作る場合は、最初に明確な基準となる実装を作ってから自分の用途にあった方向に調整していく方が容易に実現しやすいです。せっかく仕様書で設計の骨組みを説明してくれていることですし、この構造を実装のたたき台として使いましょう。

Validationのしくみ

命令列のValidationでは、主に2つの検証を行います。1つ目は、命令と即値を基に入力と出力の型をチェックする作業です。2つ目は、この型情報を活用し、スタック操作を通じて整合性を検証するプロセスです。これらを実現するためには、制御構造の情報を管理するスタックと値を管理するスタックの2種類を使うと比較的簡単に検証と実行ができる実装がつくれます。

// 擬似コードをjavascriptに変更したもので、説明のための仮実装です。
//
// opcode: i32.constなどの命令
// parameter_types: ifなどのblockに渡される引数
// result_types: ifなどのblockが期待する返り値
// args: opcode実行に必要な即値など
function validate(opcode, parameter_types, result_types, args = []) {
    switch (opcode) {
        case "i32.const":
            push_val("i32"); // 実際の値ではなく、i32という型情報のみをpushする
            break;
        case "i32.add":
            pop_val("i32"); // i32の型情報であることを期待してpopする
            pop_val("i32");
            push_val("i32"); // i32+i32の計算結果はi32であるという型情報のみをpushする
            break;
        case "unreachable":
            unreachable();
            break;
        case "if":
            pop_val("i32");
            push_ctrl(opcode, parameter_types || [], result_types || []); // ifなどのblockではControlFrameを追加する
            break;
        case "end":
            const frame = pop_ctrl(); // ifなどのblockが終了した場合はControlFrameを破棄する
            pop_vals(frame.end_types);
            push_vals(frame.end_types);
            break;
        case "else":
            const elseFrame = pop_ctrl();
            if (elseFrame.opcode !== "if") {
                throw new Error("else must follow an if");
            }
            pop_vals(elseFrame.end_types);
            push_ctrl("else", elseFrame.start_types, elseFrame.end_types);
            break;
        default:
            throw new Error(`Unsupported opcode: ${opcode}`);
    }
}

このような構造を取ることで複雑な制御構造をもつWebAssemblyのパラメーターやスタックの整合性の検証を行うことができます。7.3.2 Validation of Opcode Sequencesではこの数十行ほどの型情報を使ってValidationをするコンセプトが提示がされています。最終結果となる疑似コードだけをみるとそんなに難しくないのです。WebAssemblyの命令は一部では高レベルな制御構造があるものの、基本的には低レベル操作が多いためシンプルなValidationとなります。

実際に動作するサンプルコード

サンプルコードとしてSukoshi WebAssembly Simulatorというのをざっくりと書きました。

仕様書のAppendix記載の擬似コードからHTMLとJavaScriptに変えて実装したコードです。今回のコードではWebAssemblyモジュールのParseやバイナリフォーマットは省略して、文字列で表現したOpCodeとパラメーター類や即値をまとめた命令セットの配列を検査・処理していく形にしています。

このHTMLとJavaScriptの小さなツールは次のような手順で遊べます。

  • https://chobie.github.io/sukoshi_webassembly/ にブラウザでアクセスする
  • Instruction Setより「5.ローカル変数(検証エラー)」を選ぶ
  • Validationボタンを押してValidation Errorが出ることを確認する
  • Runを押すと未定義動作でエラーとなることを確認する
  • 2行目に["local", [], [], [1, "i32"]],// ローカル変数1 i32を宣言を追加する
  • Validationボタンを押してPassとなることを確認する
  • Runを押すと結果が3となることを確認する

ローカルにGithubリポジトリをcloneして命令を追加したりして改造していくと実装や設計上の疑問点などが浮かぶと思うので良い学習になると思います。なお、このリポジトリはAdvent Calendar用にざっと書いたコードで積極的にメンテはしないつもりです。ご了承ください。

全体で700行程度ではありますが、記事ですべてを解説するには長すぎるのでValidation実装で間違いやすい2つのポイントに絞って解説します。

ポイント1: ControlFrameの設計

現状のWebAssemblyではMulti-Value拡張を標準でサポートしており、結果としてブロックが想定するスタック状態(parameter types/result typesの両方)を宣言できるようになっています。次のWATの例ではblockがi32の値を2つ必要としていることを明示できます。

(module
    (func $add (param i32 i32) (result i32)
        (local.get 0)
        (local.get 1)
        (block (param i32 i32) (result i32) ;; 機能を説明するためだけの使い方例です
            (i32.add)
        )
    )
    (export "add" (func $add))
)

こうしたblockifなどの複雑なブロック制御を実現するためにControlFrameという情報をStackで管理します。これはRuntime実装上よくある手法です。ControlFrameの管理を正確に行わないと、brend命令の処理時に問題が発生しがちです。

私も自分のRuntimeの初期実装ではControlFrameの一部の情報を省略した結果、ブロックのネストが深くなるとスタックの不整合や誤ったジャンプ先に飛んでしまう不整合が頻発しました。この課題を解決するために、Appendixで説明されているControlFrameに情報を保持する方法を採用しています。

class ControlFrame {
    constructor(pc, opcode, startTypes, endTypes, height, condition = undefined) {
        this.program_counter = pc;
        this.opcode = opcode;
        this.start_types = startTypes;
        this.end_types = endTypes;
        this.height = height;
        this.unreachable = false;
        this.condition = condition;
    }
}

ブロックの動作を正確に再現するには開始時の仮引数と終了時に返す値を保持しておかないと実現が困難です。block, if, loopなどのブロックはすべて共通のend命令で終わることもあり、何も考えずに設計するとendbrが複雑になる要因の一つでもあります。

サンプルコードのexecution部分を見てみるとend部はpop_ctrl関数のみでシンプルです。開始時の仮引数と終了時に返す値を持っているのでほぼ共通処理でendの処理が実現できます。重要なポイントとしてはlabel_types関数で現在のControlFrameがloopの場合かどうかで開始/終了どちらの型を返すか分岐している部分です。

loopのブロックの中でbrをするとloopに戻ります。通常のblock系ではbrをすると対応するendに飛びます。ブロックの種類に応じてstart typesend typesを適切に返せるようにするとシンプルな実装にしやすいです。

// 中略
        case OP_CODE.end:
            pop_ctrl();
            break;
        case OP_CODE.br:
            if (args.length != 1) {
                throw new Error("br requires one immediate");
            }
            var result = pop_vals(label_types(ctrls.get(args[0])));
            execute_br(args[0], result, state);
            break;
// 中略
function label_types(ctrl) {
    return ctrl.opcode === OP_CODE.loop ? ctrl.start_types : ctrl.end_types;
}
function pop_ctrl() {
    if (ctrls.size() === 0) {
        throw new Error("Control stack underflow");
    }
    const ctrl = ctrls.pop();
    if (ctrl.unreachable == false) {
        var popped = pop_vals(ctrl.end_types);
        if (vals.size() != ctrl.height) {
            throw new Error("Operand stack height mismatch: " + vals.size() + " " + ctrl.height + " " + ctrl.opcode);
        }
        push_vals(popped);
    }
    return ctrl;
}

ポイント2: unreachableとスキップ処理の実装

初学段階だとunreachableといえばTrapするだけの機能だと思いがちです。実際仕様書の本文にもそうとしか書かれていません。しかし、実装的には条件分岐等で実行しない命令を正確にスキップする為にも到達不能なことを判別し正確にスキップする仕組み(ややこしいですが、これもunreachableと呼称します)が必要です。このような仕組みがないとどこまでスキップすればいいかが区別できません。

おそらく、Validationや制御構造周りの実装で一番困るのは処理を実行せずに正確にスキップする部分だと思います。Validationでは全ての分岐を通過しつつ正しく検証が行えるようにしないといけません。

function pop_val(expect = null) {
    // unreachable時はpush/popをスキップすることでそれぞれ全ての命令は通るが実際の処理はやらない、という処理ができる。
    if (ctrls.get(0).unreachable) {
        return;
    }
    /// 中略
}

実装していないとよくわからないと思うので、実例としてifの制御構造からelseまでジャンプしたい、というケースで説明します。

以前のセクションで話題に上げましたが、WebAssemblyではアセンブラでよく使われるアドレス指定のジャンプ機能がありません。どこにジャンプすればいいかはifのタイミングではわからないのです。これは、ifに対応したelseにジャンプしたいという場合にとても困ります。素直にこの挙動を実現するには実際に各種命令や制御構造を一つずつチェックして、現在のifに対応したelseendを正確に判定する必要があります。

簡単と思いきや、実際のWebAssemblyの命令列は平坦な状態です。正確に判定する処理は実装上の難しい部分となります。説明のためにWebAssemblyの疑似命令に対してインデントを入れてみます。

i32.const 0 ;; #01 if の条件
if (result i32)
  nop
  nop
  i32.const 0 ;; #06 if の条件
  if 
    nop
  else ;; 単純にelseの出現を元にジャンプ判定しようと思うと#06用のelseと誤判定します
    nop
  end
  nop
  i32.const 1
else ;; 本来#01のifから飛びたかったのはここ
  nop
  nop
  nop
  i32.const 0
end

1行目で偽となる条件であれば、13行目のelseまでの命令は無視する必要があります。しかし、2行目のifの段階では対応するelseがどこにあるのか情報を持っていません。そのために、3行目から12行目までの命令では実行せずに制御構造を正しく管理しながら命令を進ませるunreachable相当の仕組みが必要となります。

// Trapを引き起こすunreachable命令と、到達不能なことを判別するunreachable処理は別です。
// この関数は後者です。
function unreachable() {
    vals.resize(ctrls.get(0).height); // blockに入る時の高さに強制的に揃える(block前後のstack整合性確保のため)
    ctrls.get(0).unreachable = true; // 以降このブロックの実際の処理はしない
}

ControlFrameのunreachbleの情報を元に実操作を省略すればスキップする挙動が実現できます。

今回説明したネストしているifの類似系をSukoshi WebAssemblyのサンプル「6.ネストしたブロック」で試すことができます。6行目を["br", [], [], [0]]に変えると内側のブロックのみ到達不可として扱い、外側のブロックは実行されるようになります。正確に到達不能なことを判別できないと誤った値を返してしまうことがあります。

終わりに

WebAssembly Specification: Appendix 7.3 Validation AlgorithmからみるRuntime設計のポイントということでControlFrameとunreachableに絞って解説をしました。この2つのポイントを抑えておけばWebAssemblyのValidationの実装の基礎が学べます。WebAssemblyは仕様書がしっかりと書かれているので、わからない部分はAIに解説してもらいつつ実装していくと楽しく遊べますよ。

私の自作WebAssembly RuntimeはWASIp1も不完全ながら実装してあり、container2wasmが動作するまでは実装しました。WebAssemblyはお手軽に作れるわりに本格的に動いてしまうので面白いです。(Runtimeの公開は実サービスで実際に使ってから検討しようと思います)

なお、Validationはあくまでも与えられたWebAssemblyモジュールが最低限仕様に準拠してあることを確認する仕組みであり、実際に使われる環境に応じて必要なエッジケースの処理や最適化が別途必要になる場合もあります。線引きが難しい処理になるので他のVMの前例などをしらべると良いと思います。

それでは!

]]>
24431
GREE Tech Conference 2024 ご参加ありがとうございました https://labs.gree.jp/blog/2024/11/24375/ Thu, 07 Nov 2024 06:43:21 +0000 https://labs.gree.jp/blog/?p=24375 こんにちは。開発企画部の佐島です。
10月25日(金)、グリーグループの様々な技術チャレンジを紹介する GREE Tech Conference 2024 が東京ミッドタウンとYouTube Liveのハイブリッドイベントとして開催されました。
会場および開催形式は昨年と同様のため、今年新たにアップデートした点について共有いたします。
セッション内容は動画およびスライドが公式サイトにアーカイブされていますので、そちらをご覧ください!

企業が主催する技術カンファレンスのジレンマ

詳しくは昨年書いたのですが、企業側が提供したいセッションと参加者のニーズが一致しないという課題があります。

一口にエンジニアと言っても取り扱っている技術の幅が広いため、セッションの数は一見多そうに見えるが参加者にとって自分に関係ありそうなセッションは案外少ない、という現象が簡単に起きてしまいます。

昨年、この課題を解消すべくグリーグループの全体像を掴んでもらう構成の基調講演に変更したのですが、結果として基調講演で興味を持ったからセッションを聴きに行ったという声を耳にすることはほとんどなく、全くと言っていいほど手応えがありませんでした。

今年は企画段階から徹底してこの課題と向き合い、今年のテーマ「Hackpackers」にたどり着きました。
今回の基調講演「今この時代にエンジニアとマネージャーが考えるべきことを1つだけ」に倣い、今この時代に企業主催の技術カンファレンスで主催者が考えるべきことを1つだけ共有させていただこうと思います。

今この時代に企業主催の技術カンファレンスで主催者が考えるべきことを1つだけ

結論から先に言うと主催者として考えたことは「技術や人を知ってもらう接点を増やす」です。

わたしたちはこれまでセッションという形で技術チャレンジを発表してきました。しかし、限られた時間の中で1年間の取り組みを網羅することは現実的ではありません。
さまざまな事業領域で、さまざまな技術チャレンジをしているにも関わらず、みなさんに共有できる技術トピックはほんの一部でした。

もっとたくさんの技術チャレンジを共有し、もっと意見交換したいという思いから、今回はセッションに加え、グループ子会社のブースを出展することにしました。(公式ページより)

GREE Tech Conference 2024 では接点を増やす試みとしてブース出展を行いました。とはいえ接点を増やすこと自体が目的ではなく、接点を増やすことで技術や人を知ってもらう機会を増やすことが目的です。そのため、ブース出展を中心としたいくつかの施策を実施しました。
具体的にどのような施策を実施したのか、細かく見ていきたいと思います。

ブースの導入

まずはそもそもブースを導入するに至った背景を説明したいと思います。

GREE Tech Conference 2024 の企画を考え始めた頃に「技術カンファレンスのマスターガイド」という書籍が技術書典で頒布されたので早速入手し、第1章にターゲットオーディエンスの特定という項目があったので、ここを1ヶ月くらいかけて掘り下げました。

ターゲットオーディエンスを特定したのち、このオーディエンスの行動とカンファレンスに求める価値を時系列で整理し、それを実現するために必要な要件をマッピングしていきました。(いわゆるユーザーストーリーマッピングを行いました)
その結果、設定したオーディエンスはセッションだけだと参加に至らないということがかなり明確にわかりました。

この理由は明白で、ターゲットオーディエンスに設定したのは情報ではなく体験を求めている人だったからです。
そもそも情報はセッションという形で提供しており、全てのセッションはアーカイブで手に入る設計になっています。
つまり、体験はセッション以外で提供する必要がありました。

体験を提供する場として現地とオンラインという選択肢があるのですが、やるなら現地で提供しようという方向になりました。
もちろんオンラインで体験を提供する方法はいくつもあるのですが、After Partyという現地でのみ行われるコンテンツがある以上、現地での体験価値を高める方向で検討した方がよさそうというのは自然な流れだったのかなと思います。

あれこれ考えた結果、事業子会社や開発部門にブースを出展してもらい、そこで交流してもらうことで接点を増やすというアイデアにたどり着きました。

このアイデアを思いついた当初のブースのイメージというのは、いわゆるスポンサーブースであり、そもそもイベント運営を自費で賄っている企業カンファレンスに協賛という概念などなく、ブースの存在は違和感でしかありませんでした。
その後、いくつかのカンファレンスに参加し、ブースとセッションが連動していることに気づき、ブースはセッションを紹介する場、セッションはブースを紹介する場という建て付けなら上手くいくのでは?と思えるようになりました。

そこで、1社でも反対があったらこの企画はボツにしようという覚悟を決めて4月頃に各所へ打診しました。

(ブース企画書 p2より)

(ブース企画書 p5より)

幸いなことにどの部署からも「なんかよくわからないが協力するよ」という前向きな回答を頂けたため、半信半疑のままブース企画をスタートさせました。

結果的に非常に多くの接点をブースを中心に作れたこと、開催後にブース担当者から来年もやりたいという声を多数いただけたことから、上手くいったと感じています。
それよりなにより、セッションだと技術領域が幅広すぎることが悩みの種だったにも関わらず、ブースになったとたんにこの考え方が完全に逆転したことに自分自身が驚きました。

多様性のあるブースがずらっと並んでいる光景を見ると、いろんな技術領域でいろんなエンジニアが活躍してるんだなということが感覚的に伝わり、今では、あれほど頭を悩ませていた幅広い技術領域が、むしろ幅広くて良かったと感じるようになっています。これは大きな発見でした。

コワーキングスペースの導入

せっかく現地まで足を運んでもらっても、会場から離れられてしまってはサービスを提供できません。
ほとんどの参加者は仕事を休んで参加しているわけでもなく、カンファレンスの最中にリモート会議に参加したりする必要がある中でご来場いただいているものと想定し、会場から離れることなくカンファレンスを楽しんでいただけるよう会場内にコワーキングスペースを導入しました。
多くの参加者から感謝の言葉をいただけたため、導入して良かったと感じています。

実は昨年も一昨年も電源コーナーは準備していたのですが、あくまでも休憩コーナーの一角に用意しただけで、がっつり仕事をしてもいいという空間にはなっていませんでした。
今回「この部屋はカンファレンスを忘れて仕事をしてもいい場所です」というのを明確に提示することができたので、多くの方にご利用いただけたものと思っています。
(カメラマンにもこの部屋は撮影しないようにお伝えしました)

オフィスツアーの導入

カンファレンスに参加する前に、ちょっとだけ特別な体験ができれば、現地参加したくなるのでは?という仮説のもとオフィスツアーを企画しました。

当初は、VIPサービスを提供するという方向で考え、少し早く会場へ来てもらって登壇者と一緒にランチを食べるというアイデアなどを考えていたのですが、どれもこれも決定打を欠いていました。
最終的に離れた場所にあるオフィスを見学してもらい、その後バスで会場へ移動するという体験を提供しようということになりました。

バスの定員がオフィスツアーの上限となるため24名限定で募集を開始したのですが、あっという間に満席になったのでこの施策は現地参加に対して一定の効果があったのかなと思っています。

謎解きの導入

謎解きはfreeeさんの技術カンファレンスである「freee技術の日」で実施されていました。技術カンファレンスで謎解きをやるというのはおそらくfreeeさんが元祖だと思います。

当初、自分の興味のあるセッションがない時間帯の暇つぶしコンテンツぐらいの感覚で導入を決めたのですが、実際には驚くほど多くの方に楽しんでいただくことができました。
どのような内容のものだったのかについては、後日、謎を作ったエンジニアから解説ブログが出る予定ですのでここでは割愛させていただきます。
ここでは謎解きで完全に想定外だったことを2点共有させていただきます。

1点目は運営スタッフとの接点がものすごく発生したことです。
謎がわからない人への救済措置として運営スタッフに話しかけたらヒントがもらえますという、苦肉の策の難易度調整が、結果的に想定以上の参加者と謎解きを通じて接点を持つことに繋がりました。
イベントの後半に差し掛かるにつれ話しかけられる頻度が急増し、トラブル対応をするために廊下に出たにも関わらず、待ってましたとばかりに声をかけられ、非常に複雑な気持ちになりました。

2点目は徒党をその場で組んで謎解きに挑戦している参加者が多く見られたことです。
明らかにソロ参加の方同士が一緒になって謎解きに参加されているのを見て、現地参加者同士の接点を増やすきっかけになっていることを実感できました。

スナックラリーの導入

スナックラリーというのは造語でして、要はスタンプラリーのことです。
スタンプラリーはブース訪問者との接点を増やす施策としては鉄板で、ブース展示をする以上スタンプラリーは外すことのできない施策です。

当初は普通のスタンプラリーで企画を進めていたのですが、ブースにノベルティを置く方が良いのではないかという意見が出ました。
運営側から無理やりブース出展をお願いしているのに、ブース担当者に手ぶらでブースに立ってもらうわけにはいかないよねというのが発端です。
ノベルティ感のあるスタンプってなんだろうというところから始まり、いろいろ検討した結果、各ブースのロゴが入ったお菓子を集めるというアイデアが出てきたので、それをスナックラリーと呼ぶことにしました。
スタンプの台紙の代わりに、集めたスナックを入れるための入れ物を用意しなければならないのですが、正直邪魔になるだけのものにコストをかけたくないよね、ということで、最終的に台紙は共用する形に落ち着き、観覧車のモチーフとしてまとまりました。

参加された方から「エンタメ企業っぽいですね」という言葉をいただくことができ、エンタメ企業っぽい接点の増やし方を実現できたのが良かったかなと思っています。

さいごに

「技術や人を知ってもらう接点を増やす」という軸を決め、そこからHackpackersというテーマを導き、ブースを中心とした施策で当初の目的を実現することができました。
とはいえ、手放しで喜べる状況かというと、まだ課題が残っていると感じています。
具体的には、社員がブースや謎解きなどの企画にあまり参加できていなかったという点です。
外部参加者をお迎えする立場にあるという暗黙の了解が社員の参加にブレーキをかけているような気がしています。
運営としては、外部参加者であろうと社員参加者であろうと、同じようにカンファレンスを楽しんでもらいたいので、この点については来年の宿題になったかなと思っています。
(とはいえ、After Partyの抽選会で社員が当選すると、外部参加者に譲って!という気持ちになる自分もいたので、自分自身もアップデートしなければと感じています。。)

改めまして、会場とお料理をご提供いただいた東京ミッドタウンさま、会場スタッフ&配信スタッフをはじめとする関係者のみなさま、初の試みであるにも関わらず手を上げてくれたブース担当者のみなさま、登壇者のみなさま、お忙しい中、ご来場およびご視聴くださいました参加者のみなさま、本当にありがとうございました。

]]>
24375
教えることは1つでも大丈夫👌!インストラクショナルデザインを使った研修設計 https://labs.gree.jp/blog/2024/10/23486/ Tue, 22 Oct 2024 01:57:24 +0000 https://labs.gree.jp/blog/?p=23486 どーも、ちょびえです。近況としてはいろいろ有りましたが、余暇でギターを習いに行って元気にセッションなど楽しんでいます。さて、ここ数年私は新卒エンジニア向けの研修でテクニカルライティング担当しています。今日は研修のお話でも書いておこうと思います。

はじめに

近年では研修スタイルのひとつとして社内のSME(Subject Matter Expert:特定領域の専門家)が研修を行うスタイルをよくみかけます。研修設計は普段の業務と違って教育を扱います。いつもの仕事の考え方とはすこし視点を変える必要が出てくるのです。そこで、今日はID(Instructional Design:インストラクショナルデザイン)を用いた研修設計について説明していきます。

この記事では 1つだけ大事なことを研修で教えれば効果的な研修ができるとしています。たくさん教えたほうがいいんじゃないの?と一見すると矛盾しているように思える主張です。この内容について実際の研修を題材に解説をしていこうと思います。

専門的かつ複合的な話になってしまうので全体の文章量は長めです。自分に必要な部分をピックアップして読んでいただけると活用しやすいと思います。

対象の読者

  • 研修を担当するSME、もしくは講師
  • 企業での研修企画担当者

この記事では主にSMEや講師を対象としています。中小企業ではSMEが講師を兼任するケースが多くあり、この記事では両方の役割を「講師」としてまとめて表記します。

研修事例として、私が実際に行なっている新卒エンジニア向けのテクニカルライティング研修を中心として構成しています。本研修におけるテクニカルライティングの定義は「テクニカル・コミュニケーション領域の広義のテクニカルライティング」としています。

インストラクショナルデザインとは?

私の理解でいえば、IDとは、科学的根拠に基づいた教育方法の研究成果をまとめたリファレンス集です。教育の専門家による継続的な研究により、具体的な効果が証明された内容が含まれています。

IDはあくまで教育設計に関する研究成果の集大成です。企業研修での具体的な適用方法を示す体系的なガイドではありません。IDの研究成果を実際の研修に活かせるかどうかは講師の判断や工夫に委ねられています。実際にIDをうまく研修などの教育活動に適用するにはノウハウが必要です。

私たち講師としては研修を効果的にするためにIDをうまく活用できれば十分です。小さくて使いやすい研究成果を研修に取り入れることがポイントです。


インストラクショナルデザインについて知りたければこの2冊がおすすめです。私達が受けていた教育と現代教育は変わっているということが理解できれば十分です。まずは教育の方法と技術を読み、より詳細な各研究成果が知りたければ道具箱を読んでください。

Agenda

研修の企画段階から実施する前までに使えるポイントを紹介していきます。網羅的な説明ではありませんが、役立つはずです。

研修の目的を確認する(方向性の確認)

効果的な研修を実現するには設計段階で目的を明確にする必要があります。逆に、目的が不明瞭なまま設計された研修は十分な効果を得られません。研修を設計する前に関係者全員で「なぜこの研修を行うのか」という目的をしっかりと共有してください。

とはいえ、何もない状態から具体的な目的を明らかにするのは難しい作業です。もし、具体的な目的がすぐに浮かばないのであれば、研修の大まかな方向性から確認すればよいのです。そんなときは次の質問から答えを選んでみてください。

研修に参加する人に習得してほしいものごとは次のうちどれですか?

  • 技能の習得
  • 知識の習得
  • 文化の習得
  • 業務の遂行や改善に必要な行動の習得

何を選んでもよいのですが、基本的に研修では「業務の遂行や改善に必要な行動の習得」を選択します。コストをかけて行う施策なので特にこの点についての大きな異論はないと思います。


補足

知識、技能、文化の習得はそれはそれで大切です。しかし、普段の業務で使わないことを教えても使う機会が訪れません。

新卒の場合、本人の意向として他に優先したいこともたくさんあります。業務で使う機会が少ないものを教える場合は、なぜ習得してもらうのかを関係者間で考えていきましょう。

このまま話を進める前に、すこし視点を変えて講師と参加者について考えてみます。

講師が研修を計画して実施するまでには、膨大な時間と労力が必要です。例えば、グリーのエンジニア研修では90分の研修が採用されています。90分の研修内容を作成するには、講師が数十時間以上の時間を費やすケースも珍しくありません。効果のある研修内容を作るには、多数の専門書籍を読み込み、正確な根拠を導出する必要があります。この作業は結果的に通常の業務の合間に行う事になり、時間的な負担が大きくなります。

研修の参加者にとっても研修と業務のバランスは大きな課題となります。当然ですが、研修に参加すると参加者の時間が拘束されてしまいます。特に新卒研修の場合、配属前後の状況もありますし、業務との連携がうまく出来ない場合もあります。研修中にふだんの業務が気になってしまうと参加者は研修に集中できないケースも発生します。研修を実施する際には業務と研修のバランスを考慮する必要があるのです。

参加者の状況に関して、もう一歩踏み込んで考えてみます。

研修の効果を最大限に引き出すには、参加者が研修内容に対して強い内的な動機を持てることが重要です。参加者がその専門領域で困っており、学びたいという内的な動機がなければ研修も効果を発揮することが出来ません。

効果的な研修を実施するには講師だけでなく参加者も準備が整っている必要があります。これは講師の立場では解決できない問題です。会社の都合だけで研修を強行しないよう、参加者の動機や準備状況を慎重に検討してください。効果の高い研修を実現するには企画段階から様々な方面と連携して十分な準備を進める必要があります。

研修を設計する前に、これから行う研修が次の条件を満たせるかを確認してください。

  • 研修の目的を明らかにできる
  • 研修に集中できる環境を用意できる
  • 参加者が困っている内容に対して効果がある研修を提供できる

より具体的な目的を設定する

前のセクションで研修の方向性を決めました。次は成果を明確に表せる具体的な目的を設定します。

研修を効果的にするには具体的な目的を明らかにすることが重要です。目的が明確になると研修の結果が評価しやすくなり、参加者が達成すべき指標をより明確に認識できます。と、いうことで目的を設定したいのですが、曖昧なところから目的をひねり出すのは困難です。IDにはこうした場合に使えるツールがあります。目的を言語化する際に私がよく活用しているのはメーガーの3つの質問を応用した以下の問いです。

  • あなたの組織で抱えている問題の中で研修で解決できる問題はなんですか?
  • その問題は研修を行うことでどれくらい改善できますか?
  • どうやって問題が改善されたかを判断するのですか?

補足

メーガーの3つの質問はこのような質問です:

Where am I going?
目的地はどこですか?
How do I know when I get there?
たどりついたかどうかをどうやって知るのか?
How do I get there?
どうやってそこへ行くのか?

私は自分の状況に合わせて変えて使うことが多いです。シンプルが故に強力なので本当に必要なときに使ってください。

鈴木克明(1995)『放送利用からの授業デザイナー入門〜若い先生へのメッセージ〜』財団法人 日本放送教育協会 第8章 授業デザイナーとしての教師の力量より抜粋

テクニカルライティング研修の設計では次のように回答しています。ブログ向けに抽象的に書いていますが、実際は「どんな」まで具体的にしてください。

あなたの組織で抱えている問題の中で研修で解決できる問題はなんですか?
テキストコミュニケーションに関連した問題があり、それが業務に悪影響を与えている。
その問題は研修を行うことでどれくらい改善できますか?
ライティングスキルを適切に学べば、これらの問題は大幅に改善できる可能性が高い。問題によって異なるので定量的には評価しにくいが一定の改善が期待できる。
どうやって問題が改善されたかを判断するのですか?
テキストコミュニケーションを継続的に観察し、問題の発生頻度を判断材料とする。

問いに答えることで研修の目的を的確に表しやすくなります。研修設計をする際には必ずしも成果が定量的に測定できるわけではありません。とはいえ、問題点や研修の目的について考え、その結果をどう評価するかを事前に明確にすれば、研修の効果を判断する何かしらの指針が得られます。ここでは実際の研修の目的は例示しませんが、問いの答えから目的となる文章を作るのは難しくないと思います。

メーガーの3つの質問に答えてから、研修の目的を表す一文を書いてみましょう。


ちょっと一息ということで、実際のテクニカルライティング研修の冒頭をすこし紹介します。

テクニカルライティングは認知に関連した領域の課題です。自分だけで潜在的な問題に気づくのは不可能に近く、他者からの助言がないと習得が難しい領域のスキルです。だからこそ対面の研修形式が効果的になります。研修では次のような内容を導入として提示しています:


——認知メカニズムは次の図が示すように機能しています。外部からの指摘がなければ自分自身のクセに気づくのは難しいのです。

既存のテクニカルライティングの指導は主にライター向けの指導として作成されています。テクニカルライティングの指導は日本語スタイルガイド(第3版)に基づいた指導が多く、業界標準に従った文章作成の基盤を提供する一方で、ライティング能力が育っていない人にそのまま適用すると問題が発生する場合があります。


と、冒頭で問題を提起してなぜテクニカルライティングの知識や技能を学ぶのかという説明をしています。実例は省きますが、このあとに目的の提示を行っています。


教えることを1つだけ選ぶ

おそらく経験豊富な専門家が研修設計でよく陥る問題だと思うのですが…

研修設計の経験が少ない人が研修内容を考える場合には、教えたい内容が多すぎて研修資料が膨大になっていくのをよくみかけます。数百ページにわたる超大作の研修資料を作りがちです。しかし、研修という形態で膨大な知識を一度に伝えようとしても、参加者には過剰で受け取りきれないという問題があります。情報が多すぎると参加者は何が重要かを見失ってしまい、結果的に学習効果が薄れてしまうのです。教えたい内容がたくさんあるのは理解できますが、特定の知識ドメインに関する情報を羅列するだけでは参加者にとって有益な学びにはなりません。

情報は厳選しなければいけないのです。教えることは一つだけという制約を守りましょう。

厳選した内容を選ぶには、目的を達成できるひとくちサイズの行動のアイデアを多く列挙する必要があります。その中で、問題に対して効果が高いものを1つだけ選定してください。

過程は割愛しますが、私のテクニカルライティング研修では要約を教えることに決めました


教える内容を要約に決定するまでを書いたらそれだけで1記事の分量になってしまいました。
内容的に今回話したいものとは方向が違うので割愛させていただきます。

    研修前後における知識、技能、態度の差を設計する

    教える内容が仮に決まったら次は差分設計です。

    研修後の理想的な状態を具体的に設計し、目的が達成できるかを検証することが必要です。効果が薄い研修ではこの研修後の状態設計が曖昧です。講師が多くのことを教えたつもりでも、参加者が「これを自信を持って学びました!」と明確に示せない場合がよくあります。これは研修の結果として重要なポイントが不明瞭になり、何を習得すべきかがわからなくなっている状態です。このケースに対しては研修を受ける前後での知識、技能、態度の差分を明確に定義すると結果的に改善できます。


    補足

    IDの文脈で使われる態度とは意訳すると「ものごとに取り組む姿勢、もしくは状況に合わせた社会的知識」という意味です。別の補足で再度説明しますが少し難しい概念です

    実際のテクニカルライティング研修では次のような差分設計をしました。

    研修前
    (技能)直感で要約ができる
    (態度)自分の書いた文章が伝わると思っている
    (態度)コミュニケーションの問題にたまに気づく
    研修後
    (技能)体系的な手続きによって要約が行える
    (態度)伝達は難しいという前提を受け入れる
    (態度)コミュニケーションの問題に早期に気づける
    (態度)建設的に問題解決に取り組める

    この記述はブログ向けに簡易的にしています。具体的な行動レベルまで書けるとよいです。

    研修前後での差分を明確に定義すれば、何を達成すればよいか、求められている指針がある程度明確になります。もし、この差分が多すぎたり、隔たりがあまりにも剥離しているように感じる場合は、選んだ題材が研修にあっていない可能性があります。また、参加者のスキルセットはばらつきがあります。人によっては前提条件を満たしていない場合もあるので補助できるか確認してください。

    教える内容を要素分解する

    より効果的に教えるには、行動や知識をステップごとに要素分解するのが有効です。私たちが日常的に行っている行動を細かく分解してみると、実は複合的な知識や技能から成り立っていることが分かります。教える内容を要素に分けて整理すれば、複雑な内容でも再現しやすくなります。指導の際にステップごとに分解すれば、参加者がどの部分でつまずいているのかが明確になります。効果的なサポートができる状態になるのです。この手続きは「スモールステップの原理」として知られています。


    テクニカルライティング研修で説明に使ったスライドです。要素分解をして説明しています。

    要約のような複合的な行動には得意な人とそうでない人の間で大きな差があり、スモールステップの原理は問題解決の指針として活用できます。この使い方は子どもがつまずかない教師の教え方10の「原理・原則」に書かれている有用なテクニックです。

    要約の手順を分解するとおおむね次のような手続きとなります:

    手順
    見た、聞いた、感じた、主観的/客観的事実をすべて列挙する
    列挙した情報を意味的に近いグループにまとめる
    グループの情報の中で重要度の順序をつける
    グループを表す説明をひとことで書く
    ひとことの意味がおおきくずれていないかを確認する
    グループの重要度の順序をつける
    文字数制約に合わせて重要な情報グループをピックアップする
    文字数制約の中で言い換える。
    (要約時にお話の順番を変えてはいけない(省略は可)という制約をつけています)
    要約の意味がおおきくずれていないかを確認する

    このステップは私が要約をする際に行っている手順を分解したもので学術的な根拠はありません。しかし、対話力がグングン高まる!コミュニケーション・トレーニング にも大まかに似た要約の手順が記載されているので一定の学習効果があると考えます。(要約に関しては同書籍内で2ページほどでまとまっています)

    次に、各ステップにおいてどの知識や技能が必要とされているかを「ガニェの学習成果の五分類〜学習成立条件の差による分類法〜」を使って明らかにします。各ステップの分類が分かればあとの対処は比較的に簡単です。具体的にどの問題かを判断して補助を行えばよいのです。

    学習成果の分類 手順
    言語情報、運動技能 見た、聞いた、感じた、主観的/客観的事実をすべて列挙する
    知的技能 列挙した情報を意味的に近いグループにまとめる
    知的技能 グループの情報の中で重要度の順序をつける
    言語情報、知的技能 グループを表す説明をひとことで書く
    知的技能 ひとことの意味がおおきくずれていないかを確認する
    知的技能 グループの重要度の順序をつける
    言語情報、知的技能 文字数制約に合わせて重要な情報グループをピックアップする
    文字数制約の中で言い換える。
    (要約時にお話の順番を変えてはいけない(省略は可)という制約をつけています)
    知的技能 要約の意味がおおきくずれていないかを確認する

    補足

    ガニェの学習成果の五分類で書かれている分類は次の通りです

    言語情報
    指定されたものを覚える宣言的知識、再生的学習
    知的技能
    規則を未知の事例に適用する力、手続き的知識
    認知的方略
    自分の学習過程を効果的にする力、学習技能
    運動技能
    筋肉を使って身体を動かす/コントロールする力
    態度
    ある物事や状況を選ぼう/避けようとする気持ち

    認知的方略はどう学ぶべきか自分で選択するというメタ的な学習技能。
    態度はものごとに取り組む姿勢もしくは状況に合わせた社会的知識です。私の理解で言い換えると、物事に対する自身の肯定/否定の価値観から発する感情、行動でもあると思います。

    鈴木克明(1995)『放送利用からの授業デザイナー入門〜若い先生へのメッセージ〜』財団法人 日本放送教育協会 第3章 授業のねらいを分類する枠組みより抜粋

    改めて整理すると、要約という技術は次のような要素で成り立っています。

    • 該当知識ドメインからの情報の列挙・選択(状況に応じた言い換えでもあります)
    • 類似する意味間でのグループ化
    • 重要度のわりあて
    • 制約下における取捨選択
    • 検証

    要約の指導では中間成果物の作成も指導します。参加者が作成した中間生成物、最終成果物の変化を確認することでライティングに関連するどんな問題なのかを判断して指導にあたります。要約の結果だけを添削する場合、何が原因で問題となっているか判断するには手がかりが足りません。

    研修で指導する内容として1つのことを教えるのは合理的です。仮に要約という複合技能を分解して、1ステップごとに5分かけて教えるとすると8ステップで40分消費します。教える内容を1つだけに厳選するというのは時間的な制約をみても、受け取る側の認知負荷状況を見ても妥当な判断と言えます。


    制約時間が厳しい中で難易度の高い課題に取り組ませようとすると認知負荷が高くなりパフォーマンスが著しく下がります。良い学習効果が得られません。短時間で複数の違う内容を教えようとするのは無理があります。いちど教わる側を体験しなおしてみると、認知負荷の問題は痛いほどよく実感できます。

    同じ内容を別の方法から教え直す

    学習内容の保持を高めるには異なるアプローチから同じ内容を教え直すのも有効です。例えば、多くの場合に要約された文章は部分的に5W1Hを満たします。この特性を活かし、要約の練習で使用した題材から5W1Hで文章を作成する練習を行います。同じ内容を異なる角度から教え直すことで記憶への定着をより強くできます。


    補足

    5W1Hについておさらいです

    5W1Hを使うとシチュエーションを限定できます。

    • When いつ:
    • Where どこで:
    • Who だれが:
    • What なにを:
    • Why なぜ:
    • How どうした:

    あくまで5W1Hはメモ書きとしてつかうものです。メモをもとに自分の書きやすい文章を書けばよいです。順番を変えたり、各種要素の表現を変えればいくらでも文章のバリエーションが作れます。好きなように組み替えて文を作ってみてください。文の良し悪しはおいといて、自分で思いつく限りパターンを試すのが重要です。

    長い時間の体験を要約する場合は、すべての要素を書き出して分類していかないと正確な要約はできません。しかし、普段の業務で要約が必要な状況では直感と5W1Hだけでも解決できることが多くあります。

    別の方法から同じ内容を教える、すなわち、違う出発地点から対象の出来事を思い出す方法は演奏家がよく使うテクニックです。単一の方法で覚えているとうっかり演奏中にロストする(構成やフレーズを忘れて演奏を止めてしまう)時があります。人間なので仕方のないことです。別のタイミングや方法から同じ内容を再現する練習を常日頃からしておけば、ロストしたとしても第三者からわからない状態まで緩和できます。

    研修の構成及び時間配分

    効果的な研修を行うには、講師と参加者が主体的に使う時間のバランスを調整する必要があります。研修の間、参加者が適度に体を動かしたり、主体的に活動できる時間を15分から30分の周期で取り入れると集中力を維持することができます。講師が話す時間と参加者が主体的に活動する時間をバランスよく配分すれば効果的な研修が実現できます。

      項目 時間
    導入 アイスブレイク・自己紹介 5分
    ゴールの提示 2分
    既存知識から詳細 8分
    展開 (実技)要約ステップ 40分
    要約まとめ 10分
    5W1H概要(要約の別方面からの解釈) 5分
    (実技)5W1H 10分
    まとめ まとめ 5分
    質疑応答 5分

    医療者のための教える技術: オンラインと対面のハイブリッド教育研修で同様に研修時間配分のサンプルが提示されています。

    90分の研修は案外短いものです。仮に講師と参加者がそれぞれ50%ずつ時間を使う場合は講師が使える時間は45分だけです。限られた時間の中で教えるには内容を厳選しなければなりません。講師は研修の進行も行うので、そう考えると実際は45分より教育自体に使える時間は短くなります。教える内容を1つに絞る理由もここにあります。たくさんの内容を教えようとすると時間が足りず、参加者も内容を覚えきれません。

    基本的には研修では講師が参加者の困りごとを解決する具体的な説明や補助を行います。参加者が主体的に活動する時間が多くなるのが普通です。

    また、講師は研修時間内に参加者が対象のものごとを習得できているかを確認をする必要があります。参加者だけではうまく習得できているか判断できません。


    補足

    ID的な観点でみると、この構成はおおむねガニェの9教授事象に沿っています。

    • Gain Attention: 学習者の注意を引く
    • Inform Learners Objectives: 学習目標を提示する
    • Stimulate recall of prior learning: 学習の前提条件を思い出させる
    • Present the Content: 新しい情報を提示する
    • Provide Learning Guidance: 学習指導を提示する
    • Elicit Performance (practice): パフォーマンスを引き出す
    • Provide Feedback: フィードバックを行う
    • Assess Performance: パフォーマンスを評価する
    • Enhance retention and transfer: 保持と転移(別場面での応用)を高める

    保持と転移を高めるという部分は初学者向けには難易度が高すぎます。初学者向けには保持を優先したほうが効果的です。


    今回の記事ではスライドについては説明していません。もしスライドの具体的な構成で悩むようであればCygames Research研究日誌 #17を読んでください。

    参加者と気軽に話せる関係性を作る

    最後に参加者と講師の関係性についての説明をします。

    効果的な研修を実現するには参加者と講師の間でよい関係性を築くことが不可欠です。研修の成果として達成すべき状態は、参加者が自らの力で問題に取り組み、業務に必要な行動を遂行できる状態です。研修では参加者の行動やその結果に対して講師が指導や助言を行う場面が多く発生します。参加者は指摘を受ける際に抵抗感を覚える場合もあります。関係性がほとんどない状態で指導するのは現実的に困難を極めます。効果的な研修の実現には参加者と講師との間で信頼関係が欠かせません。

    藤田和日郎、飯田一史両氏の著書 読者ハ読ムナ(笑)では 「関係性をつくらないと、言われた言葉は入ってこないんだわ」 という節があります。この本では漫画家志望の方がアシスタントを経験しながら漫画家になるまで過ごしていく様子が教える側目線で描かれています。

    研修を開始する前に参加者と講師の間でよい関係性を構築してください。相手との関係性をつくるには共通項や時間が必要です。効果的な学習を行えるようにできる限り良い関係性を構築、維持できる仕組みを入れてください。


    クリエイティブ系の職業であれば読者ハ読ムナは一度読んでおくことをおすすめします。

    おわりに

    1つだけ大事なことを研修で教えれば効果的な研修ができる!ということを説明してきました。

    1つのことに集中するという考え方は研修に限らずあらゆる場面で成果を上げる基本となります。教えようとするものにはたくさんの大事なものごとがあるのは紛れもない真実です。しかし、相手に適切に伝達するにはあえて限定しないと伝わりません。すべてを伝えないからこそ効果を発揮するのです。

    研修で1つ重要なことを習得できれば、あとは自分自身の課題として行動できます。

    つまり、必要があれば自らの意思で対処できます。

    今回の記事では、「研修で1つだけ大事なことを教える」アプローチが研修を効果的にする有効な方法であることを説明してきました。この手法により参加者は習得するべき最も重要なポイントに集中できます。その結果として学習効果が向上し、業務改善や遂行が効果的に行えるようになります。

    研修設計を行う際には関係者間で目的を共有し、最も問題解決に直結できる内容を1つ選び、丁寧に教えていけるように準備を進めれば効果の高い研修が作れるのです。


    教育を考える際は哲学的な考え方もとても参考になります。

    ]]>
    23486
    YAPC::Hakodate 2024に参加してきました https://labs.gree.jp/blog/2024/10/23460/ Wed, 09 Oct 2024 03:45:01 +0000 https://labs.gree.jp/blog/?p=23460 こんにちは。開発企画部の佐島です。
    ブログを書くまでがYAPCということで、10月5日に公立はこだて未来大学で開催されたYAPC::Hakodate 2024について簡単にレポートさせていただきます。

    Open the future

    YAPCは毎回テーマが設定されるのですが、今回のテーマは「Open the future」でした。

    「未来」の名を冠するはこだて未来大で、
    あなたが思い描く「未来」を存分に語り合い、
    あなたの「未来」を切り拓くエネルギーとしてください。(公式ページより)

    YAPCにはIT業界に興味がある学生の旅費と宿泊費を支援する「学生旅費支援制度」というものがあるのですが、今回はそれに加え新たに「U25支援企画」という学生ではない若者を支援する企画も加わっています。
    先人は若者に対して希望が持てる「未来」を語り、若者は自由に自分たちの「未来」を語る、そんなイベントになるのかな、と期待しながら参加してきました。

    前夜祭

    10月4日に函館市民会館にて前夜祭が開催されました。

    ここでは惜しくも採択されなかったトークのうちの何本かがrejectconという形で発表されていました。
    さくらインターネットさんが取り組まれているガバメントクラウドの話は、近い「未来」である2025年度末までにデジタル庁の技術要件を全て満たすという挑戦で、非常に興味深いトークでした。
    また、その後に行われた座談会「必ず来る未来?IT業界での50代をどう過ごす?」というのは、若者に対してベテランが「未来」を見せる、というものでしたが、自分にとっては「現実」でありただただ共感するばかりの大変楽しい時間でした。

    本編

    会場となる公立はこだて未来大学、実は20年以上前に建築されたとのことですが、まったく古さを感じないどころか非常に先進的なデザインで、まずそこに驚きました。

    セッションをまわりつつ、スポンサーブースで出展企業の方の説明を受けたり、再会した方と廊下でお話ししたりしながら1日を過ごしたのですが、中でも特に印象的だったのが未来大の角先生のお話でした。

    けしからんライフログ研究

    角先生がこれまで取り組まれてきた研究内容を紹介するという内容なのですが、その中でもけしからんと言われたものだけをピックアップするという大変ユニークなものでした。

    例えば、服に装着した小型カメラに映り込んだ対面者の顔を数える「顔数計」の場合、公共の場でカメラを身につけるなんてけしからん!と言われ、ボタンを押すとタクシーの相乗り相手を見つけてくれる「Taxi Dash」の場合、知らない人との相乗りを斡旋するなんてけしからん!と言われたとのことです。

    図書室に滞在した時間でポイントがもらえ、そのポイントを競うことで自然と図書館へ足が向くようにするというゲーミフィケーションは「実際に本を読んだかどうかが重要。滞在時間だけで点数付けするなんてナンセンス」とこれまた叱られたとのこと。

    分野のオーソリティに「けしからん!」と言われてなんぼという精神でチャレンジを諦めない姿は素敵だなと思う一方、実際に「けしからん!」と言われる学生にとっては辛い体験のようで、落ち込んでしまうこともしばしばあるみたいでした。

    研究内容のほとんどが10年以上前の取り組みで、当時の社会通念や常識では「けしからん!」ものであったことは容易に想像できるのですが、現在ではサービス化されていてもおかしくないものばかりでとても「未来」を感じることができるトークでした。

    ブロンズスポンサー&学生支援(ライト)として協賛させていただきました

    グリーでは本イベントにブロンズスポンサー&学生支援(ライト)として協賛させていただきました。
    学生支援(ライト)としては、福岡から来られていた学生の方から「当初、学生支援で賄える範囲が交通費だけだったので宿泊は漫喫で考えていたのですが、途中で支援額が増えてホテルに泊まることができました」という話を聞いて、多少なりとも貢献できたのかなと実感することができました。

    ブロンズスポンサーとしてはGREE Tech Conference 2024の案内チラシを配布させていただきました。

    10月25日という近すぎる「未来」ではありますが、気になる方はconnpassにてお申し込みください!

    最後に

    移動に困らないよう貸切バスを手配したり、食事に困らないようキッチンカーを手配したりと運営の配慮が徹底しており、そのおかげで何不自由なく楽しむことができました。
    運営ありがとうございました!

    ]]>
    23460
    Engineers' Blog Awards 2024 を実施しました! https://labs.gree.jp/blog/2024/06/23397/ Wed, 26 Jun 2024 03:13:55 +0000 https://labs.gree.jp/blog/?p=23397 こんにちは、開発企画部の佐島です。
    Engineers' Blog Awards 2024 という技術ブログ執筆者を称える社内イベントを開催しましたので紹介したいと思います。

    開催背景

    きっかけは昨年の新卒エンジニア研修のゴールに「各社の技術ブログを書いてみる」というのを設定したことでした。
    これは社外に向けてアウトプットすることを体験することで、社内向けのドキュメンテーション能力も鍛えられるだろうという思いから始めた取り組みです。

    「各社の技術ブログ」と書いた通り、グリーグループでは各事業子会社ごとに技術ブログを運用しています。
    自分たちの会社やサービスで使われている技術の話を自主的に発信していくという文化はとても良いなと思う一方で、意識的に情報を取りにいかないと、同じグループ会社とはいえ他の子会社がブログを公開したことに気づきにくい所に課題感を感じていました。

    グループ内で他の子会社が書いたブログが相互に読まれるような環境を作りたい、というところから本アワードを開催するに至りました。

    取り組んだこと

    グループ内で運用されている技術ブログを可視化

    まず、昨年開催した Engineers' Bash 2023 の中で、来年アワードをやるよ、対象となるブログはこれだけあるよ、ということを宣言しました。
    とはいえ、一度周知したぐらいでいきなり相互に読まれるようにはなりません。

    slackチャンネルの開設


    各社のブログが公開されるたびにXでシェアするということをやっていたのですが、これだけでは届かないなと思い、社内のslackチャンネルでも情報を流すようにしてみました。
    こちらのチャンネル参加者を地道に増やすことで相互に読まれる環境が徐々に出来上がっていきました。

    Likeできるように


    noteを使って発信している記事にはLikeができるのですが、そうではないメディアを使っている子会社の記事にはLikeしたくてもできませんでした。そこで各ブログ運営者に相談してLikeできるように改修をしてもらいました。
    いまではどの記事にも何らかの形でLikeできるようになりました。

    ブログマラソン15


    Engineers' Blog Awards 2024 は2023年6月1日から2024年5月31日までに書かれたブログが対象となります。
    そのため、すべての記事が出揃った後の6月4日から Engineers' Bash 2024 開催前日の6月18日までの15日間をみんなでブログを読むための期間と定め、ブログマラソン15という名前をつけてイベント化しました。

    表彰式

    Engineers' Bash 2024 というLT大会の中で表彰式を開催しました。
    LT大会には150名弱の方々が会場に来られたのですが、そのうちの約50名がブログ執筆者でした。
    ブログ執筆者のみなさまには暗闇で光る青いリングをつけてもらい、会場内で執筆者であることが一目でわかるようにしました。

    Engineers' Blog Awards 2024

    改めてになりますが、Engineers' Blog Awards 2024について説明します。
    Engineers' Blog Awards 2024は対象期間中(2023/06/01-2024/05/31)の子会社含む全ての技術ブログより、もっとも良かった記事を称えるものです。
    今回対象となったブログ記事は156本です。
    アワードにはルーキー賞と大賞の2部門を用意しました。

    ルーキー賞

    ルーキー賞とは、対象期間中に初めてブログを書いた執筆者に贈られる賞となります。
    対象者は30名、対象記事は39本ありました。

    Engineers' Blog Awards 2024 ルーキー賞に輝いたのは「Vueコンポーネント設計とコーディングガイドラインの策定ー内定者アルバイトを終えて Now in REALITY Tech #104」を書かれた、kunadoさんでした!

    (当日体調不良のためリモートで受賞のコメントをいただきました)

    大賞

    その名から推測される通り、156記事のなかから最も優れたものになります。
    とは言うものの、大前提として技術ブログの目的はさまざまです。

    • 振り返りによる学びの最大化
    • 課題解決を公開することによるコミュニティへの貢献
    • 自社技術の認知度向上
    • 開発チームのブランディング向上
    • エンジニアカルチャーの認知度向上

    などなど、上記に限らず目的はさまざまで、どれが良いというものではありません。

    今回アワードの大賞を審査するにあたり「課題解決を公開することによるコミュニティへの貢献」という点にフォーカスして選定させていただきました。
    つまりブログを読んだ読者が、課題解決のためのヒントを得ることができそうか、という観点で選んでいます。
    あくまでも今回はこういう選定基準にしましたというだけで、これが正しいとか、技術ブログはこうあるべきだということを言いたいわけではない、という点だけ誤解なきようお願いします。

    Engineers' Blog Awards 2024 大賞に輝いたのは「Now in REALITY Tech #85 データ保護評価やりました」を書かれた、ゆーしさんでした!
    残念ながらゆーしさんは表彰のタイミングで会場にいらっしゃらなかったので、代わりにションローさんに受賞コメントをいただきました。

    (写真はゆーしさんではありません)

    最後に

    技術ブログを書く目的やモチベーションに正解などないと思います。
    ですが、書いた以上読んでもらいたいというのはすべての執筆者に共通の願いな気がしています。
    この施策を通して、少なくともグループ内で相互に読まれるような土台は整備できたのかなと思いました。
    アウトプットがもっと楽しくなるようなイベントに成長させるべく、引き続き頑張っていこうと思います!

    ]]>
    23397