AI時代に、読解力を鍛える

昨今散々言われていることだが、Webシステム開発業界のAIの進化が凄まじい。 もはや自分の仕事のほとんどのプログラムはClaude Codeによって生成され、 コードを書くという仕事は今年中に無くなりそうだ。

コードをAIが書けるようになるということは、 コーディングスピードやコードの品質による差別化が難しくなるということだ。 その結果、プログラマーはコーディング以外のところで差別化を図る必要が出てくる。 例えば、もっと上位の設計レイヤであったり、AIをオーケストレーションする能力などだ。

また、AI時代のプログラマーはコードを書く以外に、 顧客や他者とのコミュニケーション(いわゆるソフトスキル)の重要度が上がるのではないかと自分は考えている。 この分野は、AIによる代替がなかなか進まないのではないかと考えている。 なぜなら、人間はAIとビジネスをするよりも、人間とビジネスをしたい。と私は考えるからだ。

読解力について考える

では重要なソフトスキルとは何だろうか。 それは他者を尊重したコミュニケーションであったり、リーダーシップであったりなど枚挙にいとまがないが、 「読解力」というのは1つの着眼点として面白いのではないかと私は考えている。

読解力はすべてのビジネスや勉強において、基礎的な土台となる。 読解力があれば顧客からのメールを見た時に意図を取り違えないし、 ニュースや文献を読んだ時に、得られる情報を最大限に増やすことができる。

恥ずかしい話、自分はこの読解力に、全く自信がない。 なぜなら、幼少期から本を読んで来なかったからだ。 国語の教科は常に一番苦手だったし、作者の気持ちを問われたところで、そんなものは作者にしかわからないから答えようがない。と思うぐらいのひねくれようだった。 その結果、本もまともに読めない大人が出来上がり、もっと本を読んでおけばよかった。と思う毎日である。

本の紹介

というわけで、まったくない読解力を何とかするべく、本に頼ることにした。 今回読んだのは、樋口裕一著、「頭のいい」の正体は読解力 という本だ。

著者は小論文についての本を多く執筆されており、 「樋口式」という小論文のメソッドは広く知られているようだ。

この本は読解力に焦点を当て、どのようにすれば読解力がつくのかを論じている本である。 本の中には、エクササイズが多く含まれているため、受動的ではなく、能動的な読書をすることができるような仕組みになっている。

読解力を鍛えるために、文章を書く

この本の第3章のテーマは、「文章力を鍛える」だ。 文章を読解するためには、文章の構造を理解し、語彙や表現を自分のものにし、論理の組み立てを理解する必要があると本著は述べている。 そのような能力を身につけるためには、自分自身で文章を書くのが決定的な方法ということだ。

「文章を書く」ということは、一見読解と真逆のことだと私も思ったが、 著者いわく、「書こうという意識があるからこそ、文章を読む時、それが自分のものになる」とのことだ。

つまり、文章を書くためには、文章を正しく読解しなければならない。 その意識があるからこそ、文章の構成や論理構造に意識が向き、より正確に読解ができるというわけだ。

抽象と具体を行き来する

本著では、文章の型を紹介している。 例えば、最初に抽象的な結論を書き、その後に具体的な根拠を書く。というような型だ。

抽象と具体の行き来は、直近自分も注目していたテーマだった。 なぜなら、最近、細谷功著の「具体と抽象」という本を読んでいたからだ。

具体と抽象

具体と抽象

Amazon

具体と抽象を読んで、 世の中のあらゆることは具体と抽象を行き来することで 深く理解をすることができるのではないか。という仮説が私の中にあった。

例えば、システム開発でよく使う「モデル」という概念も、 具体的なシステムを抽象的な概念で表した形だ。

人間は抽象的な概念と具体的な概念を同時に織り交ぜると あらゆる説明が理解しやすいのではないか。と自分は考えている。

「頭がいいの正体は読解力」の本でも同じように 抽象→具体の型や、具体→抽象の型を使うことが良いと述べている。

具体と抽象は、読解力においても大きなヒントとなる可能性があるのだと私は思う。

おわりに

読解力は、あらゆる活動のベースとなっている。 それはWeb記事から内容を理解する時であったり、顧客とコミュニケーションをする時に意図を正確に理解したり、試験を受ける時に問題文を理解するときなど様々だ。

読解力は後から鍛えるのが難しい分、鍛えた時の効果は大きいのではないかと考えている。 特に、AI時代においては、重要度の高い能力と私は考える。 「頭のいいの正体は読解力」は、そんな読解力を鍛える方法を示してくれる本だ。

私はここ数年で本は読み始めたが、文章を書くという習慣はなかなか手を付けられていない。 手始めに、このブログで読書感想でも書いてみることで、読解力を身に着けれると良いなと思う。

一次元の挿し木 - 松下龍之介

このミス大賞を受賞した、ミステリー小説

ヒマラヤ山中で発見された2百年前の人骨のDNAが、4年前に失踪した妹のものと一致したというところから謎が始まる

「人骨をDNA鑑定すると確実に2百年前の物で、妹は4年前には確実に生きていた。ということは明らかに何かがおかしい。」 この謎があることによって、非常に読みやすい小説だった。

特に良かったのが、主人公が不自然に薬を飲んでいるという描写だった。 どこかの伏線になっているのだろうと思っていたが、これがうまくミスリードにつながる伏線になっているようだった。

後半の刑事が出てきたあたりのミスリードは非常に良くできていて、自分も見事に騙されてしまった。

結果、最後まで謎解きの予測がつかないまま読み進めることができて、非常に楽しく読むことができた。

ミステリーに加えて、明確な敵が存在するため、バトルのような描写もある。 ミステリー + バトルものラノベのような雰囲気であり、これも、読みやすさを助けていたと思う。

作者の松下龍之介さんは、理系大学院を修了された技術者の方で、 本業ではない文学で大賞を受賞されたようだ。

同じように理系大学院を修了した自分としては、 ただただ頭が下がるのと同時に、 本業の忙しさを趣味ができない言い訳にしてはいけないなと思うのであった。

innodb_buffer_pool_sizeの違いによるアクセス速度の変化

MySQLinnodb_buffer_pool_sizeはインデックスやテーブルデータのインメモリキャッシュサイズであり、かなりパフォーマンスに影響するパラメータとして知られています。

RDSなんかだと、デフォルトでDBメモリサイズの 3/4になるっぽいのですが、MySQLを自前で構築すると、デフォルトとして128MBになってしまうので、ぜひパラメータを調整したいところです。

今回は先日作った検証環境 上でinnodb_buffer_poolサイズを変えながらリクエストをさばく速度に差が出るのかを計測してみたいと思います。

計測条件

  • システム構成

    • nginx + rails + mysqlをdocker compose上に構築し、k6からリクエス
    • DB
      • メモリ1GB
      • 10万レコードのテーブルと30万レコードのテーブルをrailsからselect
  • 計測指標

    • avg http req duration P90 [s]: リクエストの応答にかかった時間の平均の90パーセンタイル
    • http req per sec [count]: 秒間リクエスト数
  • 動かすパラメータ
    • innodb_buffer_pool_sizeを 128MB (デフォ)、800MB (今回のメモリの80%)、2GB (今回のメモリの200%) それぞれ計測

結果

avg http req duration P90 [s]は 800MB < 2GB < 128MB という結果となりました。(少ないほどよい)

http req per sec [count]は 800MB > 2GB > 128MB という結果となりました。(大きいほど良い)

innodb_buffer_pool_sizeはメモリの80%がよいとされているのですが、予想通りの結果となりました。

buffer_pool_sizeがRAMの量をオーバー(今回計測の2GB)の場合、スワップ領域を使うため、スワップインアウトにコストがかかるため遅くなるとのことです。

webパフォーマンスチューニング実験環境を作る

2023年はWebパフォーマンスチューニングを趣味の一環としてやっていきたい気持ちがあって実験環境を作りたくなったので作りました。

nginx、rails、redis、mysqlというまあまあ普通の構成で、 すべてdocker compose上のコンテナとして作りました。

Webパフォーマンスチューニングをするにあたって、性能を計測するツールが必要になります。 httpの負荷試験ツールとしてはapache benchとかjmeterとか、自作とかいろいろあります。

負荷試験をするにあたって、やはり実際のワークロードに従ったアクセスを行いたいという思いがあります。

たとえばECサイトなら、ログイン、商品閲覧、カートイン、購入、ログアウトなどです。

しかしこのようなステートを持ったアクセスを負荷試験環境で行うには、そこそこの自由度を持ったシナリオを書く必要があります。

そこで、k6 というツールを使用することにしました。

これはJavaScriptでシナリオを記述できるため、プログラマフレンドリーで、自由度の高いシナリオを記述できそうです。

実際、以下のような単純な処理でhttp getとresponseのステータス検証ができました。

const res_get_items = http.get(`${TARGET_BASE_URL}/items`, {
});
    
check(res_get_items, {
  'is status 200': (r) => r.status === 200,
});

特にrailsはtemplate viewに記述されているCSRF tokenをpost時のheaderとpayloadに指定しなければCSRF検証エラーになってしまうので、htmlからtokenを抜き出す必要があるのですが、JavaScript正規表現を使って楽にtokenを抽出できました。

k6を入れたコンテナをdocker compose上に構築し、httpリクエストをnginxに向かって実行するようにしました。

計測してみる

せっかくなのでN+1問題の計測をしてみようと思います。

商品テーブル(Item)に、1対多で商品画像(ItemImage)、1対1でカテゴリ(ItemCategory)が紐づいているテーブルを作ります。

そして、商品一覧を表示するページを作ります。 1ページには500個の商品を表示し、1商品に対し、商品画像とカテゴリ名を出す必要があります。

各商品に対し、紐づく画像とカテゴリ名をロードしないといけないので、N+1問題が発生します。

通常、N+1問題に対してはeager loadという対策が有効です。今回はeager load ありとなしで計測してみました。

実際にk6で計測してみた結果、以下のようになりました。

vus(並列数?)は100で、duration(計測時間)は10sとしました。

http_req_duration avgは各リクエストの平均応答時間を表し、eager loadありのほうが11sほど速くなりました。

http_reqs は秒間リクエスト数を表していて、eager loadありのほうがおおよそ8倍のリクエストをさばけました。

また何か計測してみたいですね。

PKをauto_incrementとUUIDにしたときのinnodb insert速度比較

innodbのprimary keyにUUIDとauto_incrementを指定した時の速度差の原因についてこちらの記事にとても分かりやすくまとまっていました

techblog.raccoon.ne.jp

この記事では、PKをUUIDにしたときのinsert速度低下の原因として以下が挙げられています。

  • UUIDがランダムなため、BTreeのページ分割頻度が高くなってしまうこと、格納効率が悪くなってしまうこと
  • ランダムなUUIDに比べ、auto_incrementは新規レコード追加時にバッファプール内にキャッシュされたページがヒットする率が高いこと

勉強がてら、実際に検証環境でinsert速度を比較してみたいと思います。

検証環境

以下の検証環境を用意しました

  • MySQL (docker環境)
    • version: 8.0.30
    • information_schema_stats_expiry: 0 (information_schemaにすぐに情報を反映させるため)
    • innodb_buffer_pool_size: デフォルトの128MB
  • insertするプログラム
    • goで8並列にbulk insertするアプリを作成
    • 1レコードにはPKとmediumtextのカラム2個、3万レコードをinsertする
    • PKは以下を個別に測定
      • auto_incrementなint
      • ランダムなint
      • uuid_v4
      • ulid

検証結果

各計測結果は3回実施したものを平均しました

やはり、シーケンシャルなauto_incrementとULIDがほぼ同じぐらいの速度で、 ランダムintとUUIDはシーケンシャルなPKよりは遅くなるという結果になりました。

bulk insertのイテレーションごとに、data_lengthの推移をとってみると上図のようになりました。 data_lengthはPKのindexとページサイズで(おそらくBTreeのサイズと同じ..?)

グラフを見ると、ランダムなPK(黄色と赤色)はシーケンシャルなPK (青色と緑色)に比べ、サイズが変わる頻度が高いように見えます。

これはクラスタインデックスに対し、ランダムなページに新規レコードが割り当てられることによる、ページ分割頻度の高さが影響しているのかなという印象です。

また、バッファプールのヒット率も測定してみました。 gihyo.jp こちらの記事によると、バッファプールのヒット率は以下の式で計算できます。

(1 - ( Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests )) * 100

シーケンシャルなPKのほうがヒット率は高くなるかなとおもいましたが、結果としてはrandom intがULIDよりもヒット率が高くなる結果となってしまいました。

intとULID(varchar)ではそもそもデータサイズが違うのでキャッシュヒット率に影響しているのかもしれません。 また再測定してみたいですね。

おわりに

今回の調査でinnodbやバッファプールについて勉強になりました。元記事の著者に感謝です。