それが僕には楽しかったんです。

いろんなレイヤーに居ます

トランザクションと同時実行制御

はじめに

データベースを扱う上で、無くてはならないトランザクションについてと
それを用いた同時実行制御について「プログラマのためのSQL第4版」を読んで勉強したことをまとめる。


トランザクションと同時実行制御

トランザクションはDBを触る人なら聞いたことがあるだろう。
しかし、それらは大抵DBMSが内部でゴニョゴニョして複雑なことを単純に表現してくれている。
これはDBに限らず大抵の基盤的システムに言えることでもある。
普段は見ることがないけど、それを気にしてみるとその基盤に乗っているもののことを理解しやすくなることがある。
ここではSQLを理解する上でトランザクションという普段はみないものを見てみる。

セッション

ユーザセッションともいうが、これはDBを使うときにパスワードなどを使って接続すると確立されるもので
いったんセッションが確立されるとユーザはその上で権限の範囲でならあらゆるアクセスが可能になる。
そしてこのセッション中では0個以上のトランザクションを実行することができる。

トランザクションとACID特性


トランザクションが必要とする特性がある。それは頭文字を取ってACID特性よぶ

  • 原子性 (Atomicity)
  • 一貫性 (Consistency)
  • 独立性 (Isolation)
  • 耐久性 (Durability)
原子性

原子性は、トランザクション全体が永続的な状態であるかそうでないかという2つの状態のどちらかをとることを指す。つまり何かというと、トランザクションが完全に実行されるかされないかの2つの状態しかないということ。
SQLではCOMMIT文が実行された時に永続化され、ROLLBACK文はトランザクションを破棄しそれが始まる前の状態に戻す。これらは明示的に実行される場合もあるしDB側で処理している場合もある。
よく起こるパターンとして、SQLが何らかのエラーを吐いた時にROLLBACKが行われる。また原子性に従うので、仮に100万行をINSERTする場合に1行でも整合性制約に反していれば全てのINSERTが拒否されて自動的にロールバックされる。

このようにひとつの長いトランザクションを利用する場合はいくつか考慮することがある。
というのも、原子性に従う場合に長いトランザクションは少しのエラーでロールバックされてしまう可能性が捨てきれない。逆にトランザクションの粒度を細かくするとトランザクショントランザクションの間に別の予期しない処理が走ってしまい思わぬ結果になってしまう場合が考えられる。
これを防ぐために、SAVEPOINTというものがある。これはトランザクションの最中に設定することで限定的なロールバックを行うものである。これによりトランザクションの粒度を細かくせずに長いトランザクションを利用することができる。

一貫性

これはトランザクションが開始されたときに、DBは全てのデータ整合性制約や参照整合性制約などを満たしている状態にあるということを指す。
ただし、トランザクション実行中は一貫性が保たれているということは保証されていない。

独立性

これは非常に簡単で、トランザクションは他のトランザクションから独立し、いかなる影響も受けないという性質のことである。これはどういうことかというと、トランザクションは並列に実行しようと直列に実行されたときと同じ結果になるという意味で別名として直列性と言われる。
このあたりはトランザクションの分離レベルで触れる。

耐久性

これはDBのプログラム自体が破損したとしても、DBそのものは保全されるという性質。この性質が満たされている場合、DBは破損したとしても一貫した状態に復元することができる。ロギングとバックアップの機能がこれらを担保している。

同時実行制御

同時実行制御というのは複数のユーザが同時にDBを利用する環境でいかにトランザクションを独立させて処理するかという問題を解決するための手段である。

5つの現象

SQLが全て検索などのREADである場合は無条件にACID特性が満たされているが2人以上が同時にデータを更新作成する場合にはACID特性を満たすように動作させる必要がある。
しかし、実際には5つのパターンにおいてトランザクション同士が干渉してしまう場合がある。

この様に、5つのパターンでトランザクションは干渉する。しかし、これらが全て悪いわけではなくDBを利用するサービスによっては起こることも容認する場合がある。それに全てのパターンを防がないとするとDBは高速に動作する。
そのため実際には分離レベルを考慮してDBを設計する必要がある。

分離レベル

分離レベルとはトランザクションがどの程度干渉を受けるかを設定するのに設定することが多い。
分離レベルは以下の種類がある。

トランザクション分離レベル P1(ダーティリード) P2(ファジーリード) P3(ファントムリード)
SERIALIZABLE o o o
REPEATABLE READ o o x
READ COMMITED o x x
READ UNCOMMITED x x x

SERIALIZABLEは一般的にデフォルトで設定されている。このレベルでは複数のトランザクションが並列に実行されても直列で実行された場合と結果が変わらない。
REPEATABLE READでは同じセッションの間では同じ読み取り結果が保証される。
READ COMMITEDでは同じセッション間でも他のトランザクションが変更しコミットしたデータが読み出される。
READ UNCOMMITEDでは同じセッション間において他のトランザクションがコミットしなくても、変更したデータが見れる。

ただし、分離レベルによらずSQLの文の実行以外では全ての現象は発生しないように動作する。
また何故この3つのパターンのみなのかというと、オリジナルのANSIモデルではこの3つのパターンしか定義されていないからである。

悲観的な同時実行制御

悲観的な同時実行制御というものは、トランザクション同士は衝突するという前提の元に設計される同時実行制御のことを指す。
このような場合は必ずロックを使って排他的アクセスを許可する。ロックメカニズムには色々とありDB2 for z/OSではラッチという仕組みを使って、DB全体をロックしてしまうのではなくテーブルレベルでロックする。こうすることでテーブル全体を対象にするトランザクションではひとつのフラグしか利用しない。
さらに、テーブルを行レベルでロックすれば他のユーザはそのテーブルの他の行を利用することができる。ただしこれは効率を優先するがパフォーマンスは低下してしまう問題を抱えている。
テーブルロックと行ロックの間にあるのがページロックで、これは行のサブセットに対してロックをかける。これは物理ストレージにおいてテーブルがページという単位で実装されていることが多いからこういう名前になっている。

スナップショット分離と楽観的同時実行制御

楽観的同時実行制御はトランザクション同士がそこまで頻繁に衝突しないということを前提に設計されている同時実行制御を指す。基本的には問題が起きてから対処しようという方針になる。これを実現するのにスナップショット分離というものがある。

スナップショット分離では。それぞれのトランザクションはデータをトランザクションが開始された時点のコミット済みのデータのスナップショットから読み込む。この仕組みによりトランザクションは読み込みにおいてブロックされることは無い。ただしこの仕組みによりトランザクションによって作られた複数のバージョンを持つ可能性がある。


実際にはどういう仕組みになっているかというと、トランザクションT1はコミットする準備ができるとコミットタイムスタンプを取得する。これは全ての既存の開始タイムスタンプとコミットタイムスタンプより後の時刻になる。
トランザクションT1のコミットが成功する場合はT1の実行期間にT1が書き込もうとしたのと同じデータを書き込むためのコミットタイムスタンプを取得したT2がない場合になる。つまり、コミット早いもの勝ち戦略によってP4を防止できる。

論理的な同時実行制御

論理的な同時実行制御というのは、SQL文を互いに比較・分析してどのSQL同士であれば同時に実行することが許されるかどうかという観点において設計された同時実行制御のことを指す。
SELECT文であれば、単純な処理になりやすいがそれ以外にデータを操作するSQLの場合は非常に複雑な処理を必要とする。一見すると完璧に見えるかもしれないが、待ち時間の長いSQLをどのようにして処理していくかが次のライブロック問題を引き起こす原因となる。
これを回避するのにもっとも簡単な処理は優先度をつけることだがそれを行うと、重要度の異なる処理を割りこませるという処理が必要になる。これをしてしまうと、優先度が低いが待ち時間の長いSQLが本当に重要なSQLの前に割り込まれてしまうなどの問題を抱えることがある。

デッドロックとライブロック

同時実行制御において真っ先に思いつく困ったこととして、デッドロックがある。これはSQL同士が互いに必要とするリソースを確保してしまい相手にとって必要なリソースを開放できないという問題である。こうなってしまうと、片方のセッションを管理者が切断してロールバックするしか無い。

またライブロックという問題も起こることがある。これはひとつのセッションでリソースを専有する場合に他のセッションがそのリソースの開放を待ち、開放を待っている側がなかなか実行されないという問題である。これも起こってしまうとセッションを切ってロールバックするのが一般的な解決手法としてあげられる。
しかし、いつまでも管理者を待つこともできないのでセッションごとに優先度を決めて実行する場合も存在する。

おわりに

今回はトランザクションと同時実行制御について書いたが、これは普段DBAが責任をもつ領域でアプリケーションなど比較的高いレイヤーではあまり考えない分野になっている。
しかし、これを知っておくのはトラブルの際に役立つし何より学んでいて楽しい部分ではある。

Golangのdatabase/sqlパッケージを使っていく

はじめに

どうも、最近マイクラをかってしまい無限に時間を溶かしているけんつです。

何故かgolangが書きたくなる衝動に駆られているのですがその勢いでORMを自作しようとしたときに色々と謎が深くなってきたので
まず今まで使ってこなかった database/sql パッケージを使ってみようかと思いたったので書きます

参考文献は以下のページ
http://go-database-sql.org/overview.html

Database driver をインポートする

Golang で database/sql パッケージを使ってDBを操作するには必要となるドライバーを自分で持ってくる必要がある。
今回は対象となるDBがMySQLなので、MySQL用のドライバーをもってくる。といってもいくつかあるのでチュートリアル通り "github.com/go-sql-driver/mysql" を持ってくる

$ go get github.com/go-sql-driver/mysql

これでDBにアクセスする準備ができた。

データベースにアクセスする

DBにアクセスするには以下のようなコードを書く。

package main

import (
    "database/sql"
   _ "github.com/go-sql-driver/mysql"
)

func main() {
    //[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
    db, err := sql.Open("mysql", "sample:sample@tcp(127.0.0.1:3306)/sample")

    if err != nil {
        panic(err)
    }

    defer db.Close()
}

まず、はじめにsql.Openを呼ぶ。
ここでは利用するDBドライバを指定し、DSNを指定の形式に沿って記述する。
DSNでは dbname が require になっているがあとは必要に応じて記述する形となる。

この sql.Open 時に何かしら接続できないなどの事象が発生するとerrが返ってくるためこの部分はエラーハンドリングするべきである。
さらに接続といっても、この関数をコールし正常に終了したところで実はデータベースへの接続は確率されていない。
そのため、さらに以下の様に db.Ping() をコールして疎通確認をするべき(らしい

err = db.Ping()
if err != nil {
    panic(err)
}

加えてこの sql.DB オブジェクトのライフサイクルは長くなるように設計されている。
そのため、Open, Close を頻繁に呼び出すのではなくライフサイクルの短い関数に引数と渡すことが推奨されている(シングルトンみたいな感じ
なぜライフサイクルが長く設計されているかというと、逆に短くしてしまうとネットワークリソースを食いつぶしてしまったりTCPコネクションがTIME_WAIT状態になって残ってしまうなどの問題があるから(らしい

SQLの実行と結果の取得

まず、参考サイトと同じ構成のテーブルを作る。insert文は適当にgoでコード書いてsqlファイルにくっつけた。

CREATE TABLE users (
    id INT NOT NULL AUTO_INCREMENT,
    name varchar(30) NOT NULL,
    PRIMARY KEY (id)
);

INSERT INTO users(`name`) value ('John');
INSERT INTO users(`name`) value ('Rose');
INSERT INTO users(`name`) value ('Jack');
INSERT INTO users(`name`) value ('Mary');
INSERT INTO users(`name`) value ('Olivia');
INSERT INTO users(`name`) value ('John');
INSERT INTO users(`name`) value ('Rose');
INSERT INTO users(`name`) value ('Jack');
INSERT INTO users(`name`) value ('Mary');
INSERT INTO users(`name`) value ('Olivia');

ここまでやったら database/sql パッケージを使ってSQLを実行するコードを次のように追加する。

package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

func main() {

    //[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
    db, err := sql.Open("mysql", "sample:sample@tcp(127.0.0.1:3306)/sample")
    if err != nil {
        panic(err)
    }

    err = db.Ping()
    if err != nil {
        panic(err)
    }
    defer db.Close()

    var id int
    var name string
    rows, err := db.Query("select * from users where id < ?", 6);
    if err != nil {
        panic(err)
    }
    defer rows.Close()

    for rows.Next() {
        err := rows.Scan(&id, &name)
        if err != nil {
            panic(err)
        }
        fmt.Println(id, name);
    }

    err = rows.Err()
    if err != nil {
        panic(err)
    }
}

ひとつずつ解説していく。

まず、db.Open() が正常終了したら db.Query でSQLを実行する。値を入れたいときは ? をつかう。
これにより、そのSQLの結果が行単位かエラーで返ってくるのでハンドリングする。

次にその行の集合は rows.Nextでアクセスできる。
ここで rows.Scan() を使い、結果を変数に代入していく。

そして最後には行に対するイテレータが無事に終了していることを確認するべきらしい。

予めクエリを用意しておく

db.Queryで実行するのではなく予めプレースホルダーを含むSQLなどを用意しておき実行時にその値だけを差し替えるなんていうこともできる。

package main

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

func main() {

    db, err := sql.Open("mysql", "sample:sample@tcp(127.0.0.1:3306)/sample");
    if err != nil {
        panic(err)
    }

    err = db.Ping()
    if err != nil {
        panic(err)
    }


    stmt, err := db.Prepare("select * from users where id < ?")
    if err != nil {
        panic(err)
    }
    defer stmt.Close()

    var (
        id int
        name string
    )
    rows, err := stmt.Query(3)
    for rows.Next() {
        err := rows.Scan(&id, &name)
        if err != nil {
            panic(err)
        }
        fmt.Println(id, name)
    }
    defer rows.Close()

    if err = rows.Err(); err != nil {
        panic(err)
    }
}

さいごに

とりあえず、基本的な部分はまとめたので次はトランザクション周りをまとめていきたい。

MySQLで大量のテストデータを用意したい

はじめに

どうも、最近みたハクソーリッジという映画が最高に琴線に響いて何度も見ているけんつです

最近、データベース周りの勉強をしてみようと思ったのですが何かが足りないことに気が付きました。
そう大量のテストデータです。

なので今回は作ります。

環境構築

まずは環境を構築する。
基本的にはMySQLの話になるけども、インフラの勉強をするとき用の環境を後に整える予定なのでdocker-composeを使う。

version: '3'
services:
    mysql:
        image: mysql:5.7
        environment:
            - MYSQL_ROOT_PASSWORD=root
            - MYSQL_USER=sample
            - MYSQL_PASSWORD=sample
            - MYSQL_DATABASE=sample
        volumes:
            - ./docker/mysql:/var/lib/mysql
        ports:
            - "3306:3306"

こんな感じでハイパー雑にymlを用意する。

$ docker-compose up -d
$ mysql -h 127.0.0.1 -u sample -D sample -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.20 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

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

MySQL [sample]> 

これでログインできた。
次にinit.sqlという名前でマイグレーションを用意する

CREATE TABLE tweets (
    uid int default 0,
    user varchar(255) not null,
    body varchar(255) not null,
    liked int default 0,
    retweet int default 0,
    include_reply boolean default false,
    created_at datetime default current_timestamp
);

ツイートに見立てて簡略的に作成してみた。

ここまでが環境構築

大量のデータをどうにかして用意する

ここではPHPを使ってスクリプトを組んでしまう。

じゃあ早速用意する

<?php
declare(strict_types=1);

//user name list
$names = ['Jack', 'John', 'Smith', 'Noah', 'Harry', 
    'Sophia', 'Emma', 'Olivia', 'Ava', 'Isabella'];

//generate random body
function generateRandBody() : string {
    $result = '';
    $limit = rand(1, 100);
    for($i = 0; $i < $limit; $i++) {
        $result .= chr(mt_rand(65, 90));
    }
    return $result;
}

$limits = 10;
$count = 0;
for (; $count < $limits; $count+=1) {
     $user = $names[$count%10];
     $body = generateRandBody();
     $liked = rand(0, 1000000000);
     $retweet = rand(0, 1000000000);
     $include_reply = ($count%2 == 0) ? 0 : 1;

     $sql = "INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('".$user."','".$body."',".$liked.",".$retweet.",".$include_reply.");";
     echo $sql . "\n";
        
}

こんなスクリプトを組んでいて思ったのだが、当初はこのスクリプトから挿入させようと思ったがまともに1000万件とか挿入するとめちゃくちゃ遅い。
というわけで、↑はもうすでに変更したものだが最初の10件だけ登録してそれをひたすら単純結合させて増加させてしまおうという作戦に出た。

init.sql

CREATE TABLE tweets (
    uid int primary key auto_increment,
    user varchar(255) not null,
    body varchar(255) not null,
    liked int default 0,
    retweet int default 0,
    include_reply boolean default false,
    created_at datetime default current_timestamp
);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Jack','CSHMFFHXYIAY',101262607,788244618,0);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('John','BNYPLLSXIFXZQWZSYISTIVUDXICZZJDDTTBHUO',455425608,660218612,1);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Smith','ZVMAHSWKBYYVNDJYVCIINSTYUELEUNVDJKETJGZIAITPJJSCEPLFRTDJFDVMYKVG',155967009,720719955,0);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Noah','KIDUVZVHTQEIEHJAWRFOTXMERFASBVFNPHSSTSEMAUDPVRGHCSOJFMQHRLEFGAYPOQPKYBKBHEOXOFUERGOK',717252756,32221742,1);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Harry','NEZXVIAVARL',361169251,640449508,0);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Sophia','NBPOFOVUXPFYWGTLKIYFUUOPUGJMVWKENTEKJDKAKJLDHMFEHSYSXOLWCKLWJ',587239726,530171748,1);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Emma','IIHSNNHUQIPPXLNHEKLSLN',334524943,69008302,0);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Olivia','BHLZISBQELBLJVFZBTHBJUKAHILWJXKRHLAPYBMHXBPTSPFGVFGGLXTAEKUHMHAFHYAWQKYZ',779212237,102552681,1);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Ava','BUPFCEDTZEQQEBUFEUVDZLWKTNWRKZBLTWLXIWGXWNYEYNEHHRDLYWFAMFMEZQXCZXVSAAJWRTNFXRHYUKWPWAQCXM',40163778,286463918,0);
INSERT INTO tweets (`user`, `body`, `liked`, `retweet`, `include_reply`) value ('Isabella','OQOUHLPDOWJNRIKOYXOOYUEPEVQQENEKZFYZJBEKQPO',763927989,187392711,1);
SOURCE increase.sql;

increase.sql

insert into tweets (
    select
    tweets.uid = null,
    tweets.user, 
    tweets.body, 
    tweets.liked, 
    tweets.retweet, 
    tweets.include_reply,
    tweets.created_at
    from tweets, tweets tweets2, tweets tweets3, tweets tweets4, tweets tweets5, tweets tweets6);

このようにあらかじめ用意した10個のレコードを単純結合させることで膨大なデータを用意する

そしてこれらのファイルを実行するとこうなる。

MySQL [sample]> source init.sql;
Query OK, 0 rows affected (0.51 sec)

Query OK, 1 row affected (0.09 sec)

Query OK, 1 row affected (0.06 sec)

Query OK, 1 row affected (0.11 sec)

Query OK, 1 row affected (0.11 sec)

Query OK, 1 row affected (0.06 sec)

Query OK, 1 row affected (0.06 sec)

Query OK, 1 row affected (0.05 sec)

Query OK, 1 row affected (0.08 sec)

Query OK, 1 row affected (0.05 sec)

Query OK, 1 row affected (0.06 sec)

Query OK, 10000000 rows affected (5 min 13.20 sec)
Records: 10000000  Duplicates: 0  Warnings: 0


MySQL [sample]> source datasize.sql
+------------+--------+----------+------+--------+---------+----------+
| table_name | engine | tbl_rows | rlen | all_mb | data_mb | index_mb |
+------------+--------+----------+------+--------+---------+----------+
| tweets     | InnoDB |  9706337 |  100 |    929 |     929 |        0 |
+------------+--------+----------+------+--------+---------+----------+
1 row in set (0.00 sec)


1000万行程ある、900MB以上の容量を持つテーブルが作成できた。
何故か、30万レコード程損失があるとなっているがselect * from では1000万行あることになっている理由は謎これから調べてみる

おわりに

とりあえず、アホみたいにデカイDBは作れたのでこれを元にいろいろやっていく

LOCALの設立10周年記念パーティーに参戦してきた

はじめに

どうも、最近飛行機で東京喰種の漫画を読んでいたら感極まって泣きそうになったけんつです。

突然ですが、1/12に開催されたLOCAL設立10周年記念パーティーに参加してきたので色々書こうかなと思います。
色々といってもLOCALのことは色んな人たちが書いていたので学生部に焦点を当てて書こうかなと考えている。

学生部オフ会楽しかった。

学生部とは

一般社団法人LOCALの中にある、学生達が集まる部活の事。学生といっても最近は道内だけでなく北海道にゆかりのある人が入っていたりもする。基本的には情報系に携わる(情報系の学科や学校にいることは条件としていない)学生の集まりとなっている。

学生部では毎年、OSC Hokkaidoにブースを出展したり、部員たちで集まって進捗を叩き出す総大会などを行っている。

そしてその部の部長を2年、正式に交代する前の期間も含めると3年やっていたのが自分である。

ここ最近の学生部

この記念パーティに参加するにあたってそういえば、と思い出したのだが実は学生部も10年ほど続いていた。
自分が関わっていたのは7年目から現在に至るまでなので、10年の長い歴史を語るには恐れ多いので直近3年分を振り返ってみようかなと思う。

まず、何故入ることになったかというと弊学のヤバい先輩からLOCAL学生部の知り、気がつけば入っていた。
そこに大層ご立派な理由などは存在しなかった。

入った頃は同級生の友人と自分が最年少で、あとは先輩だけしかも3つとか上の世代ばかり。

そして丁度部長が交代ということもあって加入後、割と短い期間で気がつけば部長になっていた。
最初の総大会では、「2日でヤバい言語を作ろう」ということでインタープリターを作ったりしてみた。

次の総大会ではメンバーがある程度増えてしまい、総大会の希望を聞いても「あれがやりたい。これはやりたくない。」などみんなバラバラな希望を投げて自分を困らせるので「なんなら、好きなこと書いて本にしよう」と雑に同人誌を書き始めた。
そしてこれがOSC Hokkaido2018で展示されることになった。

一番最近の総大会では、さくらインターネットの石狩DCで合宿するという実績も解除した。


自分が部長を初めて一年目はイベントやってもあまり盛り上がらなかったり色々と大変だったが、そんなことをしている内に要領がわかり同級生の代を中心に謎の盛り上がりを見せ、それらを聞きつけたクセの強い下の世代の学生たちもイベントを開催する度に加入して今やLOCALの中でも一大勢力となっていた。

そんなこんなで近年の学生部歴の中でも一番の盛り上がりを見せている。

これから

そんなことをやってきた自分も、次の世代に学生部を任せる時期になってきた。

自分が部長の時代はこのような仕上がりになったのだが、それに左右されることなく学生部はその時に中心となる世代の色になってくれると非常に嬉しい。

ただ北海道はとても広いので、その中で情報に携わる学生たちが世代に関係なく繋がりを作れる場所であるという基本的かつ重要なことは忘れないで欲しい。
そして、今回LOCALも10周年を迎えたが学生部のこれまでの活動はLOCAL会員の方々の支えにより行えているものということも心の隅に置いておいて欲しい。

おわりに

基本的な流れは作ったので、あとは次の世代に任せる。

PHPerがいくGolang入門 A Tour of Go More types: structs, slices, and maps

はじめに

どうも、年末にアドカレを1日分書き忘れていたので急いで書き始めたけんつです。
今回も例によって全くPHPは関係無いですが、A Tour of Go の続きをやっていこうかなと思います。

Pointer

きたよ、ポインタが。こいつが来ると謎の安心感がある。
宣言、アドレスの参照などは大体Cと同じ。

package main;

import "fmt";

func main() {
    var number int = 12345;
    var pointer *int = &number;

    fmt.Println(pointer);
    fmt.Println(*pointer);
}

Structs

構造体、ここまでくるとCと大体変わらなくなってくる。
構造体そのものもそうだし、構造体をポインタを通して操作することができる。

package main;

import "fmt";

type Vertex struct {
    X int
    Y int
}

func main() {
    var v Vertex = Vertex{1, 2};
    var p *Vertex = &v;

    fmt.Println(v);

    p.X = 3;

    fmt.Println(v);
}
$ go run structs.go 
{1 2}
{3 2}

こんな感じで。さらに Name: のようにフィールドの一部だけを列挙することができる。

Arrays

配列ですよ、こいつがあればなんとか競プロで戦えそう。

package main;

import "fmt";

func main() {
    var primes [5]int;
    primes[0] = 2;
    primes[1] = 3;
    primes[2] = 5;
    primes[3] = 7;
    primes[4] = 11;

    fmt.Println(primes);

    numbers := [2]string{"hello", "world"};

   fmt.Println(numbers);
}

型の明示的宣言に未だに違和感があるがなんとか。

Slice

これ、Pythonのスライスと似たようなことをやっている印象あるんだけど無駄に長いので結構大変だった。
スライスは配列は固定長なのにたいして可変長のやつをさす
使い方はPythonのそれと基本的には同じ。

package main;

import "fmt";

func main() {
    primes := [6]int{2, 3, 5, 7, 11, 13};

    var slice []int = primes[1:3];
    fmt.Println(slice);
}

また、スライスは配列の部分列に対する参照を指しているので元となる配列の値を変更するとスライス先もその変更が反映されてしまう。

スライスは次のようにするとその配列を参照するスライスを作ることができる。

slices := []int{2,3,5,7,11,13}

これは右端の配列に対するスライスが生成される

さらに、組み込み関数のひとつであるmake関数を使用することによっても背製することができる。

a := make([]int, 5)

スライスは可変と言ったが、すでにあるスライスに対して要素を追加する場合はappend関数を使う。

Range

for などで range を使用する場合スライスやマップに対してひとつずつ反復処理をする場合につかう

package main;

import "fmt";

func main() {
    var pow = []int{1, 2, 4, 8, 16};

    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v);
    }
}

Map

mapが使えるみたいなんだが、割と難解な気がする
なぜここでmake関数を使っているのだろうか…

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m map[string]Vertex

func main() {
	m = make(map[string]Vertex)
	m["Bell Labs"] = Vertex{
		40.68433, -74.39967,
	}
	fmt.Println(m["Bell Labs"])
	fmt.Println(m)
}

function values

関数もまた変数であるため、他の変数のように渡すことができる。

package main;

import "fmt";
import "math";

func pow(f func(float64, float64) float64) float64 {
    return f(3, 4);
}

func main() {
    
    hypot := func(x float64, y float64) float64 {
        return math.Pow(x, y);
    };

    fmt.Println(hypot(2, 2));
    fmt.Println(pow(hypot));
}

個人的に濃かった激動の2018年を振り返ってみる

はじめに

どうも、最近PS4を買ってしまいゲーム三昧な年末を全力で過ごしているけんつです。
今年は、というか主に今年の後半に色々あったのでちょっと振り返ってみようかなと思ったので書きます。雑に。

初めてインターンに参加した

まず、今年の始めに人生で初めてインターンなるものに参加した。
詳しいことは以下の記事にまとめてある。
rabbitfoot141.hatenablog.com


何をやったかって言われるとプロトコル・スタックにDHCPクライアント機能を追加するということをやった。当時、ScalaJavaしかやっていなかった人間がいきなりカーネルモジュール書いたり、パケット書いたりとがっつりレイヤー下げたのでかなり苦労したけどめちゃいい経験になった。(詳細は後ほど) 参加したのが3月の話だったんだけども、年の始めから全開で飛ばしていた気がする。
ここでは本当によい経験が積めたと思っている。

サーバサイド強化期間

インターン期間中に色々あって、昨年からフルリモートで携わっているWebサーバサイドを少し見なおした。具体的には、PHPで書かれたOSSのコードを1から読んでみたり、それを元にテンプレートエンジン作ってみたりと色々やった。インターンを終えて働き方やサーバサイドに取り組む姿勢みたいなものを見なおした。これも結構大きな出来事というかよくやったな当時の自分、というように思う。
記事はこのあたり。自信のサーバサイドはPHPと共にあり。といった感じになっている。

rabbitfoot141.hatenablog.com

rabbitfoot141.hatenablog.com

rabbitfoot141.hatenablog.com

rabbitfoot141.hatenablog.com

研究室に配属された

今年も後半になった頃、研究室に配属された。機械学習とか画像処理をやっている研究室なんだけども、何故か今そこでWebシステムを作っている。しかもオンプレだけで。結構しんどいんだけど、このあたりから運用を考えた技術選定やシステムアーキテクチャに目を向け始めた。
あとは単純にインフラ周りの知見を深めたり、何故かロギング周りをごりごり調べたり、一番大きなものはJavaScriptが少しかける様になった。Redisも楽しい。
エンジニアのエゴで必要以上にごつ盛りにした技術スタックとか運用を無視した構成や最新の技術を使い倒したいから使うとか本当にその後に触る人によってはその人が地獄を見る可能性があるので良くないと思うようになった。このあたりの経験は開発だけでなく、運用や保守、設計などの一段広い視野を持ち始める良いきっかけになっていると思う。

逆求人なるものに参加した

ついに就活ですよ。サポーターズさんのやつに行ってきた。
rabbitfoot141.hatenablog.com

これは驚きの連続だった。詳細は↑を。

2018年は

あんまり意識はしていなかったが今年はカーネルモジュールを書き、パケットを書き、サーバサイドを書き、(ここには書いてないが)インフラをガッツリ触り、システムの設計をしてとバックエンドごつ盛りのような生活を送っていたことにこの時点で気がついた。
序盤はこれまで不足しがちなCS周りの知見を貯めて、その後はサーバサイドを書き、サーバサイドをもっと書くためにインフラを構築し、それらでプロダクトを作るために設計や運用を考える。完璧な流れが今年は出来上がっていた。
作ってきたものが、githubでスター3桁どころか2桁ですら届かないようなものだったがただただ面白くて作っていた(特にカーネルモジュールとしてEchoサーバを作るとか)から全く気が付かなかった。けど、割とフロント以外はフルスタックな感じのとてつもなくいい経験を積めた一年になった。

来年の目標

今抱えている、タスクをひとまず全て片付けたい。
あと、進路が決まりそうなので決まったらそれにむかって更に精進する。
そして卒業する。

あと、これは絶対やりたいなと思っているんだけどフルスタック感全開できたからよく思うんだが自分には「これなら出来る」っていう分野が何一つ無いので自信をもってこれは出来ると言える分野を見つけたい。

こんな感じでやっていくので、来年もどうぞよろしくお願いいたします。
それでは良いお年を。

PHPerがいくGolang入門 A Tour of Go Flow control statements: for, if, else, switch and defer

はじめに

Muroran Institute of Technology Advent Calendar 2018 20日目の記事です。

どうも、2018/12/20という未来から来ましたけんつです。
引き続き A Tour of Go を進めていこうと思います。

ただ昨日、気がついてしまったのですがこのあたりの構文系の解説を日本語で書くまでもないので
今回はひたすらコードで語っていこうと思います。

難解な部分だけ、適宜解説はさみます。

そして今回も全くPHP関係ないです。

For

package main;

import "fmt";

func main() {
    sum := 0;
    for i:= 0; i < 10; i++ {
        sum += i;
    }
    fmt.Println(sum);
}
package main;

import "fmt";

func main() {
    sum := 0;
    for i:= 0; i < 10; i++ {
        sum += i;
    }
    fmt.Println(sum);

    for sum < 1000 {
        sum += sum;
    }
    fmt.Println(sum);
}
||
初期化と後処理ステートメントを省略できて、こうなるとwhile文と同様の扱いとなる

* If

>|go|
package main;

import "fmt";
import "math";


func sqrt(x float64) string {
    if x < 0 {
        return sqrt(-x) + "i";
    }
    return fmt.Sprint(math.Sqrt(x));
}

func main() {
    fmt.Println(sqrt(2), sqrt(-4));
}

このようにかける。
if 文より気になったのがsqrt関数の末尾でreturnしている部分、ここを return string(***);とすると
convertできない旨が帰ってきた。


続いて、評価式の前に簡単なステートメントがかけるらしいということがわかった。
ただし、そこで宣言された変数などはif~elseのスコープ内でしか使えない。

package main;

import "fmt";
import "math";


func sqrt(x float64) string {
    if x < 0 {
        return sqrt(-x) + "i";
    }
    return fmt.Sprint(math.Sqrt(x));
}

func pow(x float64, n float64, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v;
    }
    return lim;
}

func main() {
    fmt.Println(sqrt(2), sqrt(-4));
    fmt.Println(pow(3, 2, 10), pow(3, 3, 20));
}

Switch

package main;

import "fmt";
import "runtime";

func main() {
    fmt.Print("Go runs on ");

    switch os := runtime.GOOS; os {
    
        case "darwin":
            fmt.Println("OS X.");
        case "linux":
            fmt.Println("Linux.");
        default:
            fmt.Printf("%s.", os);
    }

}

これも普段となんら変わらないけど、特徴としてbreakが自動的に提供されているため
明示的に記述する必要がない。

もう少し正確に書くなら、条件にマッチした部分で自動的にbreakされる。

defer

きたこれ案件。
deferは、渡した関数の実行を実行している関数が終了するまで遅延させる。

package main;

import "fmt";


func main() {
    defer fmt.Println("world");
    fmt.Println("hello,");
}

2つ以上の関数をdeferに渡すとそれはスタックされるので LIFO の順番で処理される
>|go|package main

import "fmt"

func main() {
fmt.Println("counting")

for i := 0; i < 10; i++ {
defer fmt.Println(i)
}

fmt.Println("done")
}
|

$ go run multi_defer.go 
done
9
8
7
6
5
4
3
2
1
0

おわりに

今回は一般的な制御構文ですごくさくっと、なにも書くことなく終わってしまったので
次回以降はもうちょい骨のあることしたい。