oinume journal

Scratchpad of what I learned

初めてIELTSを受験した

IELTSってなに?

国際的な英語の試験。大学の入学の条件に使われたり、国によっては永住権を申請する時のポイントとして使われるもの。

会場

自分は東京会場で、場所は幸ビルディングという新橋付近の貸し会議室っぽいところだった。

持ち込みできるもの

中に持っていけるものは

  • 鉛筆
  • 消しゴム
  • パスポート
  • パスポートのコピー

のみOK。なお、鉛筆はお願いすれば試験の最中に試験官が削ってくれるので、鉛筆削りは持っていなくても大丈夫。

会場に着いてから

  1. 荷物を預ける
  2. IDチェック
  3. 試験室に入室

という流れ。

会場のビルの会議室でビニール袋に荷物を詰めて預ける。8:40にはこの荷物置き場が物理的に閉鎖されるので、それまでに会場に着いて荷物を預ける必要がある。

その後IDチェック。これをやったあとにトイレに行こうとすると再度指紋認証する必要がありめんどいので先にトイレは済ませておいたほうが良い。IDチェックでは指紋を採取されさらにカメラで写真を取られる。パスポートの顔写真と同一人物かも当然チェックされる。

終わると、試験官と一緒に試験する別の部屋に行く。

感想

受験者は予想通り若い人が多かった印象。驚いたのは、20%ぐらいは外国人(見た目で明らかに日本人じゃないとわかる人)だったこと。アフリカ系の人やインド系の人が多かった。もしかしたら中国人や韓国人も結構いたのかもしれない。カップルで受験している人もちらほらいた。自分みたいな40代のおっさんは1%ぐらいしかいない雰囲気だった。

IELTSの試験

意外なことに

  1. Writing
  2. Reading
  3. Listnening

という順番だった。というのは、参考書ではListening, Reading, Writing, Spreakingの順番で問題が構成されていたから、順番が全然違うことにびっくりした。もろもろのアナウンスが終わって9:20頃から試験がスタートした気がする。

  • Writing: 40分
  • Reading: 60分
  • Listening: 30分

という時間配分で、12:30ごろには終わった。それぞれのパートの合間で問題や解答用紙を回収する時間があるので、少し休めるのは良かった。TOEICと比べるとこの休憩時間があるおかげでそこまで疲労感を感じなかった気がする。なお、Speakingは午後にあり、自分は15:20開始だったので、昼ごはんを食べて新橋駅付近のカフェで時間を潰していた。

Writing

トピックは

  • Task1: 友人に仕事(求人)を薦める時に、仕事内容、なぜその人にオススメするのか、求人への応募方法をレター風味で書く
  • Task2: 保育園に子供を預けることと、預けないで家族が育てることについての意見を述べよ

というもので、どちらも自分にとっては馴染みのあるものだった。なので、書く内容を日本語でまとめておいて、あとはそれをひたすら英語に訳すというスタイルで書いていった。Task1は130 word以上で、ギリギリかけた。一方、Task2は250 word以上なので、圧倒的に時間が足りなくて文章の途中で終わってしまった。Writingは「字数が足りないからあとから文章を付け足す」ということがとてもやりにくいので、英文を書き始める前にちゃんと書く内容を決めておくことが大事だと思った。Computer Basedなテストだったら文章の間に別の文を挿入したりするのが簡単にできたりするんだろうか、などと考えた。

とにかく仕事ではこういう風に文章を書くことがあまりないので(デジタルなら後からいくらでも文章を付け足したり入れ替えたりできるので)、時間配分も含めて痛いミスをしてしまったように思う。Task2はおそらく100 wordぐらいしか書けていない。

Reading

最後のsection以外はそこまで難しくなかった。あと時間が圧倒的に足りないかなと思ったけど、ギリギリ終わるレベルだった。例えばTOEICだと明らかに時間内に終わる量ではないので、それに比べれば時間的な厳しさはなかったように思う。

Listening

Section1はたしかに一番簡単だった。ただ、選択式ではなく単語を埋めるタイプなので、冠詞をつけるかつけないかでけっこう悩んだ。Section4がやはり最高に難しくて、2,3問合っていればいいか、ぐらいのノリ。

Speaking

これは外国人の試験官に連れられて小さい会議室で実施された。"How are you?" とか少し雑談してice breakしてから本試験が始まる。内容としては

  • 学生 or 仕事している?
  • 仕事で一番興味深いことはなに?
  • どんな音楽を聴く?
  • 音楽を聴くときは一人で聴くのが好き?それとも他の人と聴くのが好き?

というような質問をされた。案の定Speakingが一番難易度が高いというか、普段の実力値が出てしまうのでこれが一番点数低そうだなと思った。

まとめ

試験の2週間前にIELTSの問題集をやって「うわっなにこれ難しすぎ」と絶望していたのだけど、本試験は意外と難しくなく、少しホッとした。スコアは10/13頃に送付されるらしいので、どのぐらいできているのか楽しみ。5.0〜6.0の間ぐらいだと思うんだけどどうなることやら。

【音声ダウンロード付】IELTSブリティッシュ・カウンシル公認問題集

【音声ダウンロード付】IELTSブリティッシュ・カウンシル公認問題集

Problems when updating MySQL from 5.7 to 8.0

Introduction

I updated MySQL from 5.7 to 8.0. There were some problems when updating. This is just a memo how to solve the problems.

InnoDB deprecated file format parameters

These parameters are deprecated in 8.0.

  • innodb_file_format
  • innodb_file_format_check
  • innodb_file_format_max
  • innodb_large_prefix

ref: MySQL :: WL#7704: InnoDB: Remove deprecated file format parameters in 8.0

Query cache parameters are deprecated

These parammeters are deprecated in 8.0.

  • query_cache_limit
  • query_cache_size

ref: https://mysqlserverteam.com/mysql-8-0-retiring-support-for-the-query-cache/

innodb_support_xa

innodb_support_xa is deprecated as well.

ref: MySQL :: WL#8843: Deprecate and remove the parameter innodb_support_xa

Can't create users with GRANT

You can't create users with GRANT operation. For example, you need to change

GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON db.* TO 'user'@'%' IDENTIFIED BY 'yourpassword'

like this:

CREATE USER IF NOT EXISTS 'user'@'%' identified by 'yourpassword';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON db.* TO 'user'@'%';

ref: How to grant all privileges to root user in MySQL 8.0 - Stack Overflow

default-authentication-plugin

The default value of default-authentication-plugin is caching_sha2_password. If your MySQL driver doesn't support the authentication method, you'll get an error this authentication plugin is not supported. mysql_native_password is the default value in MySQL 5.7 so you can specify it like this:

default-authentication-plugin = mysql_native_password

Other problems I've faced with

TABLE_NAMES from information_schema

My program executes following query.

SELECT table_name FROM information_schema.tables WHERE table_schema = 'mydb';

+------------------------------------+
| TABLE_NAME                         |
+------------------------------------+
| event_log_email                    |
| following_teacher                  |
| goose_db_version                   |
| lesson                             |
| lesson_status_log                  |
| m_country                          |
+------------------------------------+

The result of the query is TABLE_NAME in 8.0 although it was table_name in 5.7. As a result, I changed my query like this:

SELECT TABLE_NAME AS table_name FROM information_schema.tables WHERE table_schema = 'mydb';

Goにおける並行処理 - channel編

はじめに

これはGoにおける並行処理 - goroutine編 - oinume journalの続きの記事。goroutineに引き続き、Goの並行処理を支える重要な概念であるchannelについて説明する。

channelとは?

channelはメモリに対するアクセスを同期するためやgoroutine間の通信(データの受け渡し)として使うものである。Go言語による並行処理では

水が流れる川のように、チャネルは情報の流れの水路として機能します。値はチャネルに沿って 渡され、そこから下流に読み込まれます。

と説明されている。例えば以下のプログラムでは、main関数からHello WorldをChannel messageChに書き込んで、それを無名goroutineが読み取って出力している(Playground)

package main

import (
    "fmt"
)

func main() {
    messageCh := make(chan string)
    go func() {
        fmt.Println(<-messageCh)
    }()
    messageCh <- "Hello World" 
}

channelを使う上で覚えておくべき基本的なものとしては以下である。

  1. channelにデータを書き込む送信側とデータを受け取る受信側が存在している必要がある
    • 例えば上のプログラムで fmt.Println(<-messageCh) がない場合、 fatal error: all goroutines are asleep - deadlock! というエラーになる
  2. 受信処理はchannelに書き込みがあるまでブロックされる
    • 上のプログラムで messageCh <- "Hello World" の書き込みがない場合は、goroutineとして実行されている無名関数がずっとブロックされ何も起こらずにプログラムが終了する。
  3. 送信処理はchannelがいっぱいの場合、空きができるまでブロックされる
  4. channelは一般的にはmakeで生成する
    • make(chan, 3) のように、バッファをつけて生成することができる。この例だと3個のバッファがあるため、4個目を書き込む時にブロックされる。
  5. 送信専用channelと受信専用channelがある
  6. channelをcloseする

channelのブロック

通常であれば、goroutineはsync.WaitGroupを使って起動元のgoroutineにjoinするようにしないと、タイミングにもよるがほぼ確実に実行されずにmain関数が終了されてしまう。しかし、上のHello Worldを出力するgoroutineでは、必ずHello Worldと出力される。これはいったいなぜだろうか?

理由は、channelからデータを受信する場合データが送信されるまでブロックするからである。つまり、上のプログラムの実行順序は以下のようになる。

func main() {
    messageCh := make(chan string)  // (1) channelを宣言
    go func() { // (2) goroutineの実行
        fmt.Println(<-messageCh) // (4) channelからデータを受信(送信されるまでブロックされている)
    }()
    messageCh <- "Hello World"      // (3) channelへデータを送信
}

つまりchannelとgoroutineを使うことで、何かデータを受信するまで待って、受信したら処理を開始する というようなWorker処理を簡単に実装することができる。

channelのバッファ

ch := make(chan int, 3)

のようにmakeの第2引数にバッファを指定することができる。バッファを指定することで、channelの読み込みが一度も行われなくても、キャパシティが3のバッファ付きchannelであればgoroutineは3回まで書き込みが可能になる。バッファを指定しない場合は1回書き込むとブロックされる。

送信・受信専用channel

channelを宣言する時に何も指定しなければ書き込みも読み込みも可能になるが、以下のように宣言することで書き込み専用・読み込み専用のchannelを作ることができる。

  • chan<- int: 読み込み専用
  • <-chan int: 書き込み専用

この文法は、関数の引数にchannelを受け取る場合や戻り値としてchannelを返す場合に「これは書き込みしかできない」「これは読み込みしかできない」ということを明示できるため有用である。

channelのclose

channelは使い終わったらcloseすることができる。closeされたchannelからはデータを読み込むことはできない。例えば以下のプログラムは

  • goroutineでchに書き込み、最後にchannelをcloseしている
  • mainの中のforループでチャネルchからデータを読み込んでいる

というものであるが、closeされているのでその段階でchannelからの読み込みはブロックされずにforループが終了する。

package main

import "fmt"

func main() {
    ch := make(chan int)
    go func() {
        defer close(ch)
        for i := 1; i <= 5; i++ {
            ch <- i
        }
    }()

    for value := range ch {
        fmt.Printf("%v ", value)
    }
}

実践的な例

最後に実践的なchannelの使い方を載せておく。上で紹介した以下のchannelの特徴を使っている。

  • バッファ
  • 送信・受信専用channel
  • close

このプログラムが行っていることは、URLをchannelで送って、http.Getした結果をchannelで返すというものである。main関数の中で以下のgoroutineを立ち上げて、最後にchannel resultsを読み込んで標準出力にurl, status, errを出力している。

  • httpGetを実行する(channel urlsを読み込んで、resultsに書き込む)
  • channel urlsに書き込む
package main

import (
    "fmt"
    "net/http"
)

type result struct {
    url    string
    status int
    err    error
}

func main() {
    urls := make(chan string, 3)
    results := make(chan result, 3)

    // Consumer
    go httpGet(urls, results)

    // Producer
    go func() {
        targetURLs := []string{
            "https://journal.lampetty.net/entry/review-2019-07",
            "https://journal.lampetty.net/entry/review-2019-06",
            "https://journal.lampetty.net/entry/review-2019-05",
            "https://journal.lampetty.net/entry/review-2019-04",
            "https://journal.lampetty.net/entry/review-2019-03",
            "https://journal.lampetty.net/entry/review-2019-02",
            "https://journal.lampetty.net/entry/review-2019-01",
        }
        for _, url := range targetURLs {
            urls <- url
        }
        close(urls)
    }()

    for r := range results {
        fmt.Printf("url = %v, status = %v, err = %v\n", r.url, r.status, r.err)
    }
}

func httpGet(urls <-chan string, results chan<- result) {
    for url := range urls {
        resp, err := http.Get(url)
        if err != nil {
            results <- result{
                url: url,
                err: err,
            }
            return
        }
        results <- result{
            url:    url,
            status: resp.StatusCode,
        }
        _ = resp.Body.Close()
    }
    close(results)
}

まとめ

Goの並行処理の基礎となっているchannelについて説明した。次のパートでは、応用編としてgoroutineとchannelを使った並行処理の実践的なパターンを紹介したいと思う。

Go言語による並行処理

Go言語による並行処理

  • 作者: Katherine Cox-Buday,山口能迪
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2018/10/26
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

Goにおける並行処理 - goroutine編

はじめに

Goでは、goroutineとchannelが言語仕様として組み込まれているため、他の言語に比べてとても並行処理のコードが書きやすい。この2つの基本的な動作原理についてまとめた自分用のメモである。(channelについては別の記事で書く予定)

goroutineとは?

  • goroutine(ゴールーチン)とは、他のコードに対して並行に実行している関数のこと
  • goroutineはgoというキーワードを関数呼び出しの前に置くことで起動できる。簡単!
  • あらゆるGoのプログラムでは、main関数を動かすためのgoroutineが必ず存在する。つまりGoを支えるとてもプリミティブなものである。

goキーワードを使ってgoroutineを動かすとても簡単な例としては以下になる。

package main

import "fmt"

func main() {
    
go sayHello() 
   // 他の処理を続ける
} 

func sayHello() {
    fmt.Println("hello") 
}

このコードは以下の2つのgoroutineで動作している。

  1. main関数を動かしているgoroutine
  2. main関数から分岐した、sayHello関数を動かしているgoroutine

goroutineの正体

ではgoroutineとは何なのだろうか?Javaなどの他の言語をやってきた人からすると、これはスレッドとして実行されているのか?と気になるところだが、実態はスレッドではない。Go言語による並行処理 P.38では以下のように説明されている。

ゴルーチンは(他の言語にも似た並行処理のプリミティブは存在しますが)Go 特有のものです。ゴルーチンはOSスレッドではなく、また必ずしもグリーンスレッド――言語のランタイムにより管理されるスレッド――ではありません。ゴルーチンはコルーチン(coroutine)として知られる高水準の抽象化です。コルーチンは単に「プリエンプティブでない」並行処理のサブルーチン(Goでは関数、クロージャー、メソッドに相応)です。つまり、割り込みをされることがないということです。かわりに、コルーチンには一時停止や再エントリーを許す複数のポイントがあります。

ゴルーチンが独特なのは、ゴルーチンが Go のランタイムと密結合していることです。ゴルーチンは 一時停止や再エントリーのポイントを定義していません。Go のランタイムはゴルーチンの実行時の振る舞いを観察し、ゴルーチンがブロックしたら自動的に一時停止し、ブロックが解放されたら再開します。これによってある意味ゴルーチンをプリエンプティブにしていますが、ゴルーチンがブロックしたときにしか割り込みません。このようにランタイムとゴルーチンのロジックには美しい関係性がありま す。以上のことから、ゴルーチンは特殊なコルーチンと考えられます。

goroutineはGoのランタイムにより管理されるため、Goのコードから一時停止したりレジュームすることはできない(channelやcontextを使えば動作を止めるようにキャンセルすることは可能)。これがコルーチンとは違う点だと思う。また、goroutineとして関数を実行してもスレッドが新しく作られるわけではないので、スレッドよりも軽量である。

Go がゴルーチンをホストする機構は、いわゆる M:N スケジューラーと呼ばれる実装になっています。 これは M個のグリーンスレッドを N個の OS スレッドに対応させるものです。ゴルーチンはグリーンスレッドにスケジュールされます。グリーンスレッドの数よりも多い数のゴルーチンがある場合には、スケジューラーはゴルーチンを利用可能なグリーンスレッドに割り振って、これらのゴルーチンがブロックした場合には他のゴルーチンを実行するようにしています。

fork-joinモデル

Goではfork-joinモデルと呼ばれる並行処理のモデルに従っている。

  • fork: プログラムの特定の場所で子供の処理を分岐させて、親と平行に実行させること
  • join: 分岐した時点より以降に、平行処理されている親と子が再び合流すること

図で表すと以下のようになる。

ここで先ほどのsayHelloを使ったgoroutineの例を見てみる。

func main() {
    
go sayHello() 
} 

func sayHello() {
    fmt.Println("hello") 
}

sayHelloはgoroutineとして実行された後にjoinしていないので、helloと表示されずにこのプログラムは終わってしまう。ではmainに合流させるにはどうしたらよいだろうか?

一番簡単なやり方は以下のようにsync.WaitGroupを使うことである。

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(1) // 1. 起動するgoroutineの数を伝えるため、カウンターに1を足す
    go func() {
        defer wg.Done() // 2. カウンターから1をマイナスする
        sayHello()
    }()
    wg.Wait() // 3. ここでsayHelloのgoroutineが終わるまで待つ
    fmt.Println("finished")
}

func sayHello() {
    fmt.Println("hello") 
}

https://play.golang.org/p/-uPFeh_37mB

まとめ

これがgoroutineの基本的なことの全てである。goというキーワードを使うだけで並行処理が簡単に書けるということがわかるはず。

Go言語による並行処理

Go言語による並行処理

  • 作者: Katherine Cox-Buday,山口能迪
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2018/10/26
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

2019年7月の振り返り

アウトプット

7月も小ネタだけどブログを書いた。

path-shrinker

path-shrinker というものを作った。ターミナルのプロンプトの /Users/go/src/github.com を /U/g/s/github.com みたいにするやつ。zshのoh-my-zshではプラグインがあるんだけど、Bashでは見当たらなかったので作ってみた。

Cloud Next'19

GoogleのCloud Nextに参加した。AWSも含めてこういうクラウド系のイベントには参加したことがなかったのだけど、GoogleのSREの人のセッションは参考・刺激になったので自分も実践していきたいと思った。AWSに比べてGCPはServerlessがかなりいけてるという印象を持っていたのだけど、それが再確認できたのも良かった。

OpenCensus

lekcijeのパフォーマンスチューニングのためにOpenCensusを導入して、Stackdriver Traceでlatencyを見れるようにした。もともとボトルネックはわかっていたのだけど、httptrace.ClientTrace + OpenCensusを導入してHTTPクライアントでどこが遅いかも視覚化できたのはいい勉強になったと思う。けっきょくHTTP KeepAliveを長めに設定して少しだけパフォーマンスが良くなった。

睡眠

1日6時間以上寝る、ということを今年の目標にしている。ヘルスケアアプリによると6月は平均6時間25分だった。ただ、毎日6時間以上の睡眠ができているわけではないので、引き続きこれは課題がある。特に平日は6時間寝れている日がほとんど皆無。

英語

IELTSの攻略本を見ると、語彙力がすべてのパートにおいて重要なので、頑張って英単語を地味に覚えるようにしている。

アルゴリズム

特に進捗なし

Go, gRPC, SQLの復習

引き続き Go言語による並行処理 を読んでいる。

まとめ

7月は体調を崩してしまって会社を2日を休んでしまったのと、あまりアウトプットはできてないので頑張っていきたい。