20201130のGoに関する記事は7件です。

Go言語で作成した実行ファイルをEC2でデーモンとして実行する

概要

Go言語でbuildした実行ファイルをEC2上に配置しdeamon化します。
そうして、EC2の起動時に自動的にGoでビルドしたWebサーバを実行させます。

経緯

Go言語+GinでビルドしたWebサーバをEC2上で構築していたのですが、
EC2の起動時に自動実行されなかったのでいろいろと試した結果、デーモン化しinit.dで自動起動させました。

1 ビルドした実行ファイルのデーモン化

ディレクトリ
/etc/systemd/system/サービス名.service

[Unit]
Description=説明文

[Service]
User=root
Group=root
WorkingDirectory=ディレクトリ
ExecStart=ディレクトリ/実行ファイル名
Restart=always
KillMode=process

[Install]
WantedBy=multi-user.target

サービスの確認

systemctl status サービス名.service
systemctl start サービス名.service
systemctl stop サービス名.service

上記でサービスの起動が出来たらデーモン化は完了です。

2. init.dでサービスの自動起動を設定

ディレクトリ
/etc/int.d/app_start
--------------init.d 内容-------------------------
#!/bin/sh
# chkconfig: 2345 99 10
# description: deamon wakeup scrupt

case "$1" in
 start)
       date >> /home/user/start.txt
       echo "start!" >> /home/user/app_start.log
       sudo sh /home/user/scripts/application_start_server.sh
       ;;
 stop)
       echo date >> /home/user/app_stop.log
       sudo sh /home/user/scripts/application_stop_server.sh
       ;;
  *) break ;;
esac

application_start_server.sh の部分は直接コマンドを書いても良いですし、
今回はシェルスクリプトを実行させました。
(サービスの起動・停止時に時間をファイルに出力しています。)

3 確認

EC2を再起動しサービスが正常に起動していれば完了です。

所感

インスタンスの再起動は物理マシンの起動と挙動のでその辺りが原因でバッチ処理ではうまく動かなかったのだと考えています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gormとの破局、そしてFacebook/entとの出会い

golangとの出会い

御岳山のミツバツツジが紫の花をつけはじめ高山でも徐々にコケモモやアヤメなどのいろとりどりの花が見て取れるようになった時期、RESTよりパフォーマンスの良いgRPCをチャレンジしようという流れでgRPC使うならサーバサイドは折角だしgolang使おうとなった辺りがgolangとの出会い。
散々他所でも言われていますがgolangくんは付き合ってみるとシンプルを売りにしている反面結構頑固な所が多く割と融通の効かない子です。それ故にアプリケーション全体のアーキテクチャ設計で頭使う必要があり無茶な実装はlinterで弾きやすくレビュアーにとっては優しくたまに不満はあるけれど全体としてみれば嫌いじゃないかなという気持ちでここまで付き合いが続いています。
golangくん個人開発ではそこまで旨味なさそうだけど複数人の開発では使えば使うほど味が出てくるスルメみたいな子。golangはいいぞ。
なお本記事は本質部分ではない部分のerrorチェック処理等は全部カットしてあります。ご了承ください。

Gormとの出会い

そんな頑固一徹で素材の味を活かすのが持ち味なgolangくんなのですが、流石にデータベースを扱うときには実装者にconnectionをあんまり意識させたくないし、データベースから取得したレコードをstructにMappingするために結果セットをfor statementで回すのは面倒くさい上にあまりシンプルな実装にならないだろうなと思い、Active RecordのようなORMを採用しようと最初から決めていました。
当初は自分もそこまでgolang界隈に詳しくなかったのでORM libraryの選定の際ひとまずGithubのスターの数多いし開発止まってなさそうなのでGormを使ってみるかみたいな感じでGormを選んだのがGormくんとの出会いです。

Gormと過ごした日々

Gormを導入した時、標準のsqlパッケージのようにQueryを投げてrows.Next()で回すような手続き型ではなく

type Group struct {
    ID    int
    Name  string
    Users []User
}

type User struct {
    ID      int
    GroupID int
    Name    string
}

func main() {
    db := connection()
    defer db.Close()

    query := `
SELECT users.id, users.name
FROM groups
INNER JOIN users ON groups.id = users.group_id
WHERE groups.name = ?
`
    groupName := "LiGHTs"
    rows, _ := db.Query(query, groupName)

    var users []*User
    for rows.Next() {
        var user User
        if err := rows.Scan(&user.ID, &user.Name); err != nil {
            log.Fatal(err)
        }
        users = append(users, &user)
    }
    fmt.Println(users)
}

func connection() *sql.DB {
    // 省略
}

メソッドチェーンで記述できQueryの構築からDBへの発行structへのMappingも行ってくれるため「おっ結構便利なやつだな」というのが最初の印象。

type Group struct {
    ID    int
    Name  string
    Users []*User
}

type User struct {
    ID      int
    GroupID int
    Name    string
}

func main() {
    db := connection()

    var group Group
    if err := db.Preload("Users").
        Where(Group{Name: "LiGHTs"}).
        Find(&group).Error; err != nil {
        fmt.Println(err.Error())
        return
    }
    fmt.Println(group)
}

func connection() *gorm.DB {
    // 省略
}

その後は記事を更新しつつGormと仲良くなるべく日々を過ごしていました。

gormと仲良くなりたい(1) - gorm の Where/Update で struct を使いたい
gormと仲良くなりたい(2) - datetimeでマイクロ秒(6桁)まで格納/取得する

Gormとの破局

GormのDELETEで誤爆しないために
のような記事を書いていた頃、実装の際に壁にぶち当たりドキュメントを調べてGorm本体のソースを追ってるうちに「あれ?もしかしてGormのイケてないのでは?」と徐々に思うようになってきてしまいました。
そして、イケてないと思い始めると思い当たる節がいくつも出てきてパッと思いつく限り挙げていくと

主に コーディング時に気付かずbug埋め込んでしまうタイプの罠が多い事ドキュメントが割と不親切で読んでも解決できずに結局gorm本体のコードを読む事が多い所が物凄い不満が溜まっていき敢え無く破局へと追い込まれていきました。

破局の根本の原因としては

  • Gormは「コードの記述量を減らす事」を最優先として設計されており「コードに書かれていない事も"意図せず"ある程度いい感じに動いてしまう」ようになっていること
  • Select結果のMapping先だけでなくWhere条件やUpdate対象までinterfaceで引数を受け取りreflectionで頑張って解析するという作りにしている為に、Gormの内部でstruct内のfieldのゼロ値がゼロ値なのかnilなのか判断できないという問題にぶち当たり、結果的にGormに値がゼロ値のstructを渡した場合初めて分かるタイプのbugが量産されてしまったこと
  • TABLE JOINやPreloadの仕様が独特で内部でreflectionを行っている都合「field名(=物理column名)」を意識しなくてはいけないこと。あとそれに伴いPreload時にはstringでfield名を定義する必要があること

ここらへんなのかなと思っています。
unit testでの値のケースでたまに漏れているとdeploy後にbugに気づく事が度々あり実装者が常にそこを意識して実装するのは結構ストレスが高かったです。

Facebook/entとの出会い

Gormの反省を踏まえてgenericsがなくて*4型にうるさいgolangとしてのlibraryの在り方としてはinteface型で引数を受け取りreflectionを多用して頑張る方向ではなく、codeで定義書いてその定義からcodeをgenerateする方向が向いているのではないかと思いはじめた矢先Facebook/Entに出会いました。

Gormからの乗り換え先を探す際の最低限の基準として考えていた

  • schema定義をcodeで書けること
  • schema定義からQuery生成関連のcodeをgenerate出来ること
  • schema定義が正しいかgenerate時にチェックしてくれること
  • 静的型付けされたSetter/Getterが用意されていること
  • generateされるcodeがある程度カスタマイズ出来ること
  • Datadogを使用してOpenTracingの出力が確認できるようGo Datadog Traceに対応しているか

の条件を満たし、かつまあ企業が運用しているならGormがやらかしていたデータ全消し仕様なんて妙なトラップもないだろうという打算もありつつ新しく付き合い始めました。

さっきまでの実装をFacebook/entで実装すると

schema定義ここから ---->

schema/groups.go
// Groups holds the schema definition for the Groups entity.
type Groups struct {
    ent.Schema
}

// Fields of the Groups.
func (Groups) Fields() []ent.Field {
    return []ent.Field{
        field.String("name"),
        field.String("leader").Optional(),
    }
}

// Edges of the Groups.
func (Groups) Edges() []ent.Edge {
    return []ent.Edge{
        edge.From("users", Users.Type).
            Ref("group"),
    }
}
schema/users.go
// Users holds the schema definition for the Users entity.
type Users struct {
    ent.Schema
}

// Fields of the Users.
func (Users) Fields() []ent.Field {
    return []ent.Field{
        field.String("name"),
    }
}

// Edges of the Users.
func (Users) Edges() []ent.Edge {
    return []ent.Edge{
        edge.To("group", Groups.Type).
            StorageKey(edge.Column("group_id")).
            Unique().
            Required(),
    }
}

<---- schema定義ここまで
schema定義はテーブルごとに必要になります。

以下が実装の本体。

main.go
func main() {
    ctx := context.Background()
    client := connection()

    group, err := client.Groups.Query().
        WithUsers().
        Where(groups.Name("LiGHTs")).
        Only(ctx)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(group.Edges.Users)
}

func connection() *ent.Client {
    // 省略
}

コード生成するのでコード量自体は10倍くらいに増えますが、schema定義さえしっかりしていれば
必要そうなAPIが大体実装されているので、例えばGormで散々悩まされたゼロ値とnilの区別の問題もschema側でfieldをOptionalで定義すれば

schema/groups.go
// Fields of the Groups.
func (Groups) Fields() []ent.Field {
    return []ent.Field{
        field.String("name"),
        field.String("leader").Optional(),
    }
}
main.go
    group, err = client.Groups.Query().
        WithUsers().
        Where(groups.LeaderIsNil()).
        Only(ctx)

のようにIS NULL検索なども問題なく行えるようになります。
※念のためGormでIS NULL検索が出来ないわけではないです。あくまでWhereにstructを渡した場合の話。

Facebook/entを使ってみての所管

Facebook/entを使い始めた早1か月ちょい、まだ使い始めたばかりでいくらか検証の余地は残ってはいるのですがFacebook/entの特徴と実装してみたところの感想としては以下の通りです。

  • まだv1はリリースされていない絶賛開発中
  • Go templatesを利用したQuery生成APIのgenerateがメイン
  • Go templatesを記載する事により一部生成codeの拡張が可能
  • ドキュメントが比較的丁寧(だが、Edgeの説明はあんまり親切じゃない)
  • 実装時の制約となるvalidation checkの仕組みが整っている
  • generate時のerrorメッセージが分かりやすい ←割とありがたい
  • schemaからgenerateされたQuery生成APIは静的型付けが行われる
    • ただしselectで取得するcolumn絞ったりするとreflectionが使用されたりはする
  • TABLE JOIN statementは書けない
    • Graph Traversalという独特の思想(結合対象のテーブルに対して対象のid一覧をin句に指定したselectを自動で行う)
  • 複合PKに対応出来ないわけではないがあまり考慮はされていない
  • PKの名前がidじゃない場合schemaの記述にもひと手間工夫が必要
  • PKの名前がidじゃない場合のO2O周りの結合の挙動が怪しい
  • Upsertには現状対応していない 協議はされている
  • SELECT FOR UPDATEのようなqueryが書けない 協議はされている

物理schemaがAPI毎の概念schemaに合わせて設計されているならば恐らく問題なく使用できる子であるとは思いますが、頑張ってJOINしないとデータが取れないテーブル設計やパフォーマンス重視のマジカルなテーブル設計の場合は厳しそうです。ここら辺はRawで生SQLが書ける分まだGormの方が柔軟性がありました。

幸いにも今回は元々APIに合わせて物理schemaが設計されており、Gormからの乗り換えで気になったのはGorm側の仕様でテーブル名が複数形になってる事とO2Oでの結合くらいでした。
後者はPKの名前と型がid intではない場合、定義は出来ますがO2OのEdgeを定義するとGraph Traversalでrecordが取得できなかったり、そこだけはちょっと予想外な動作をしていたためもしかしたらバグなのかもしれません。Facebook/entの実装はgoではなくgo templateなので読むのがちと辛いですがバグの内容が分かったらPRを出そうかと。
どちらにせよ現在開発中なのでこの問題は時間経過で解決されるとは思います。

Graph Traversal周り分かりにくい所はありますが現状問題なく使えてはいます。
とはいえUpsertは実装でどうにかなりますがSELECT FOR UPDATE辺りは早めに欲しいかな。

クリスマスが終わったらまた破局しないようにお祈りしつつ 今回はここまで。

補足

*1 全件DELETE

ちなみにgorm v1での動作です。v2ではerrorになるので突然消されることはなくなりました。

*2 ゼロ値

string = "" int = 0 bool = false 等の要するに初期値の事。

GormではUpdateの際に更新値としてstrutcを渡せるが、struct定義がゼロ値の場合は更新されない。公式ドキュメントにもサラッと記載がある。

type User struct {
    Name   string
    Age    int
    Active bool
}

// Update attributes with `struct`, will only update non-zero fields
db.Model(&user).Updates(User{Name: "", Age: 0, Active: false})

上記の値でUpdateを行った場合は何も更新されない

*3 双方に互換性がない

例えばgorm v1ではPrimaryKeyのstruct tagの指定がprimary_keyだがgorm v2ではprimaryKeyである
Gormは2つのバージョンがありドキュメントは分かれているという前提条件を知らずにドキュメントを見ていたりすると想定通りに動かなくてハマる。何故ドキュメントを2つに分けたし。

*4 genericsがない

golang 次期メジャーバージョン辺りでは正式に実装されるはずなので大幅に便利になりそう。
The Next Step for Generics

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

あまり知られていないGoのスライス/配列の初期化

初めに

先日、こちらの動画で見たことがない配列の初期化をしていたので、それについてわかったことを解説してきます。

配列の初期化

動画のほうでは次のような配列宣言をしていました。

package main

var array = [...]int{0, 4:1, 2, 1:1}

func main() {
    println(len(array))
}

一般的な配列やスライスの宣言は以下のパターンがあると思います

var slice1 = []int{1, 2, 3, 4}
var slice2 = make([]int, 1, 4)
var array1 = [10]int{1, 2, 3, 4}
var array2 = [...]int{1, 2, 3, 4}

これらはよく見かけるパターンですが、{0, 4:1, 2, 1:1}というのは見たことがなく、どう読めばよいのかわかりませんでした。
これについてHajime Hoshi (星一)さんに教えていただきました。結論からいうと以下の処理と同じことをしています。

var array = [6]int{}
array[1] = 1
array[4] = 1
array[5] = 2
fmt.Println(array)

解説

配列、スライスではインデックスを指定して値をセットできます。
つまり{0, 4:1, 2, 1:1}は、0番目に04:14番目に値15番目に21番目に1をセットしています。
これは構造体の初期化時にフィールドを指定できるのと同じです。

gorilla := Animal{Name: "gorilla", Age: 28}

最後に

普通にGoを書いていたらこのような書き方は使わないんですが、構造体の初期化と同じ理屈と考えるとすごく腑に落ちます。
Go、奥が深い。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ローカルでMySQLコンテナに大量INSERTしていたら"unexpected EOF"になった時の話

はじめに

データ削除バッチのベンチマークを取るため、ローカルのMySQLコンテナにデータを800万レコードほどINSERTしていたところ、 packets.go:36: unexpected EOF というエラーに遭遇しました。その時に調べたことや対処したことなどをまとめます。

環境:

  • Go 1.15.2
  • go-sql-driver/mysql v1.4.1
  • MySQL 5.7.30

対処と原因

MaxLifetimeを設定する

packets.go:36: unexpected EOF でぐぐってみると、結構ヒットしたので比較的よくある現象のようです。それらによると、 SetConnMaxLifetime を設定すれば解消するとのことでした。

SetConnMaxLifetime に関してはこちらの記事で説明されています。その名の通り、接続の寿命を設定するもののようです。
Re: Configuring sql.DB for Better Performance

MySQL では wait_timeout という設定で接続がサーバーから切られる恐れがあります。また、OSやルーターが長時間利用されていないTCP接続を切断することもあります。どのケースでも、 go-sql-driver/mysql はクエリを送信した後、レスポンスを受信しようとして初めてTCPが切断されたことを知ります。切断を検知するのに何十秒もかかるかもしれませんし、送信したクエリが実行されたかどうかを知ることもできないので安全なリトライもできません。
こういった危険をなるべく避けるためには、長時間使われていなかった接続を再利用せずに切断し、新しい接続を使うべきです。 SetConnMaxLifetime() は接続の寿命を設定するAPIですが、寿命を10秒に設定しておけば、10秒使われていなかった接続を再利用することもありません。

引数に与える秒数に関しては、

SetConnMaxLifetime() は最大接続数 × 1秒 程度に設定する。多くの環境で1秒に1回接続する程度の負荷は問題にならない。

とあったため、とりあえず1秒を設定しました。

つまり、こんな感じの実装です。

db.SetConnMaxLifetime(time.Second)

これにより、エラーは発生しなくなりました!!

なぜこの設定で問題解消されるのか

MaxLifetime の値はデフォルトでは無制限です。
一見すると、コネクションはできるだけ使いまわした方が再接続のオーバーヘッドも少なく、良いように思われるのですが、接続時間が長すぎることのデメリットも多くあるようです。いくつかの記事でそのことが説明されていますが、例えば、こちらの記事ではこのように説明されていました。

コネクション回数が減る代わりにアイドルのコネクションが必要なくなってもキープし続ける負荷があったり、DB起因でコネクションが中断されたり、フェイルオーバしたりした際に、外部要因で既に切れたコネクションを持ち続けて切断の検知に時間がかかったり、新しいコネクションに切り替えて欲しい際に切り替えがなかなか起こらないというリスクを含んでしまいます

sql.DBのチューニング方法

つまり、今回は「大量INSERT中になにかしらのDBコンテナ側の起因でコネクションが中断された」と推測されます。
DB側でコネクションが中断されても、GoクライアントはFIN通知を受け取るわけではないのでそのコネクションは生きていると判断します。Goクライアントからクエリパケットを送る際に初めてパケット喪失をして気づき、 unexpected EOF のエラーを受け取る格好になります。
このようにならない為に、Goクライアント側で MaxLifeTime の設定をし、Goクライアント側で接続を1秒毎に切っておく対処を追加することで解消されるのかと。

なぜMySQLでエラーが発生したのか

ここまででは、 unexpected EOF が発生する理由と解決方法はわかったのですが、そもそもなぜMySQLでエラーが発生してしまったのかという根本原因が分かっていません。

MySQLコンテナ内のログを確認してみると、 ibuf cursor restoration fails というエラーログがでていることが分かりました。これ自体の意味はよく分かっていないのですが、 MySQL5.7.31でfixされたようです。
https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-31.html

最終的な解決手段

そこで、当時最新であったMySQL5.7.32にアップデート(ローカルにある5.7のimagreをいったんrmして、再起動)して、 SetConnMaxLifetime の実装を削除して、無事動作することを確認しました。
つまり、バージョン上げるだけで解決しました。

これから

本番環境やステージング環境ではAWSのAuroraを使っており、Auroraでも同様の問題が発生してしまうのかの確認が必要です。
それはこれからの話なので、進展ありましたら、記載します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HUGOでシンプルなドキュメントサイトをつくる

エイチームフィナジー@Minashoです。
本記事は、Ateam Finergy Inc. Advent Calendar 2020の2日目に公開された記事です。

はじめに

静的サイトジェネレーターHUGOを使って、ドキュメントサイト(wiki)を制作します。

はてなブログやWordPress等のCMS(Contens Management System)でもドキュメントサイトを作ることは可能です。
しかし、ブログ用CMSだとデザインの制約が大きいという問題があり、WordPressはサーバーとドメインを用意する必要があり、コストが発生します。

静的サイトジェネレーターであるHUGOは、学習コストはありますが、無料で利用できます。
GitHub Pages等のホスティングサービスで無料公開も可能です。

今回使用するテーマのデモサイト

https://themes.gohugo.io//theme/hugo-theme-techdoc/

静的サイトとは

まず、静的サイトとは、ユーザーのアクセスに対して用意してあるHTMLを返すだけのサイトです。
いつ誰が閲覧しても、同じページが表示されます。
したがって、ユーザーによって内容が変わらないwikiやブログ、コーポレートサイトが適しています。

私が所属しているエイチームフィナジーのコーポレートサイトも静的サイトです。

一方、掲示板やSNSなど、見るタイミングやユーザーによって表示内容が変わるサイトは動的サイトで実現できます。
サーバーにリクエストがあった時に、DB内のデータやテンプレートからHTMLファイルを生成し、ユーザーに返します。

静的サイトのメリットは、表示速度が早いこと。
デメリットは、1ページごとにHTMLファイルを用意する必要があり、手間がかかること。

このデメリットを解消してくれるのが、静的サイトジェネレーターになります。

静的サイトジェネレーターとは

名前の通り、静的サイトを生成してくれるツールです。

コンテンツを書いてビルドを実行すれば、ヘッダーやフッター、サイドバーなどを含んだHTMLファイルを自動で生成してくれます。

ブログサービス同様、コンテンツを書くことに集中できるというわけです。

HUGOとは

HUGOは静的サイトジェネレーターのひとつです。
GO言語で作られており、他の静的サイトジェネレーターよりもビルドが高速であるという特徴があります。

また、デザインテンプレートが豊富な印象です。

10分でサンプルサイトを制作する

それでは、HUGOを使って実際にドキュメントサイトを作っていきましょう!

HUGOをインストール

Homebrewをインストールしていない方は事前にインストールしておきます。

Terminal
# Homebrewインストール
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

次はHUGOをインストールします。

Terminal
# HUGOをインストール
$ brew install hugo
# バージョンを確認
$ hugo version

サイトを作成する

Terminal
$ hugo new site sample_document
# 以下の構成でsample_documentディレクトリが生成されます
sample_document
├── archetypes
│   └── default.md
├── config.toml
├── content
├── data
├── layouts
├── static
└── themes

テーマを適応する

今回はtechdocというドキュメントのHUGO公式テーマを使用します。
HUGOの公式テーマはGitHubで管理されているので、アップデートに対応するためサブモジュールでcloneします。

Terminal
# gitインストール
$ brew install git
Terminal
# sample_document配下に移動
$ cd sample_document
$ git init
# themesにテーマをclone
$ git submodule add https://github.com/thingsym/hugo-theme-techdoc.git themes/hugo-theme-techdoc

続いて、HUGOの設定ファイルであるconfig.tomlを編集します。
テーマをcloneしたらthemes/hugo-theme-techdoc/exampleSite/にサンプルの記事や設定ファイルが置いてあるので、そちらを利用します。

サンプル用に編集した以下のコードをconfig.tomlにコピペしましょう。

今回使用しない変数や設定は#でコメントアウトしています。
(様々な機能が用意されているので慣れてきたら読んでみましょう)

config.toml
baseURL = "https://example.com"

languageCode = "jp"
DefaultContentLanguage = "jp"
title = "サンプルドキュメント"
theme = "hugo-theme-techdoc"

hasCJKLanguage = true
metaDataFormat = "yaml"

defaultContentLanguage = "jp"
defaultContentLanguageInSubdir= false
enableMissingTranslationPlaceholders = false

[params]

    # Source Code repository section
    description = "これはサンプルドキュメントです"
    # github_repository = "https://github.com/thingsym/hugo-theme-techdoc"
    # version = "0.9.6"

    # Documentation repository section
    # documentation repository (set edit link to documentation repository)
    github_doc_repository = "https://github.com/thingsym/hugo-theme-techdoc"

    # Analytic section
    google_analytics_id = "" # Your Google Analytics tracking id
    tag_manager_container_id = "" # Your Google Tag Manager container id
    google_site_verification = "" # Your Google Site Verification for Search Console

    # Open Graph and Twitter Cards settings section
    # Open Graph settings for each page are set on the front matter.
    # See https://gohugo.io/templates/internal/#open-graph
    # See https://gohugo.io/templates/internal/#twitter-cards
    title = "Hugo Techdoc Theme"
    images = ["images/og-image.png"] # Open graph images are placed in `static/images`

    # Theme settings section
    # Theme color
    # See color value reference https://developer.mozilla.org/en-US/docs/Web/CSS/color
    custom_font_color = ""
    custom_background_color = ""

    # Documentation Menu section
    # Menu style settings
    menu_style = "open-menu" # "open-menu" or "slide-menu" or "" blank is as no sidebar

    # Date format
    dateformat = "" # default "2 Jan 2006"
    # See the format reference https://gohugo.io/functions/format/#hugo-date-and-time-templating-reference

    # path name excluded from documentation menu
    menu_exclusion = [
        "archives",
        "archive",
        "blog",
        "entry",
        "post",
        "posts",
    ]

    # Algolia site search section
    # See https://www.algolia.com/doc/
    algolia_search_enable = true
    algolia_indexName = "hugo-demo-techdoc"
    algolia_appId = "7W4SAN4PLK"
    algolia_apiKey = "cbf12a63ff72d9c5dc0c10c195cf9128" # Search-Only API Key

# Global menu section
# See https://gohugo.io/content-management/menus/
# [menu]
#   [[menu.main]]
#       name = "Home"
#       url = "/"
#       weight = 1
#
#   [[menu.main]]
#       name = "Twitter"
#       url = "https://twitter.com/thingsym"
#       weight = 2

# Markup configure section
# See https://gohugo.io/getting-started/configuration-markup/
[markup]
    defaultMarkdownHandler = "goldmark"
    [markup.goldmark.renderer]
        unsafe= true
    [markup.tableOfContents]
        startLevel = 2
        endLevel = 6
        ordered = false

[outputs]
    home = ["HTML", "RSS", "Algolia"]

# Algolia Search configure section
[outputFormats.Algolia]
    baseName = "algolia"
    mediaType = "application/json"
    isPlainText = true
    notAlternative = true

[params.algolia]
    vars = [
        "title",
        "summary",
        "content",
        "date",
        "publishdate",
        "description",
        "permalink",
        "keywords",
        "lastmod",
    ]
    params = [
        "tags",
        "categories",
    ]

テーマを適応してHUGOを起動してみます。

Terminal
$ hugo server -D

起動したら下記URLにアクセスしてみましょう!
http://localhost:1313/
スクリーンショット 2020-11-29 19.54.01.png

Ctrl+Cで一旦サーバーをストップさせましょう。

コンテンツを追加する

コンテンツを追加していきましょう。
コンテンツはcontent配下に設置します。

ファイル冒頭の---で囲まれた部分は、Front Matterと呼ばれます。
様々な変数を扱えるのですが、ここではタイトル・日付・公開or非公開を扱います。

content
├── _index.md     # トップページ
└── chapter1      # 親要素
    ├── _index.md # /chapter1で表示
    └── 1.md      # /chapter1/1で表示
_index.md
---
title: "Chapter 1"
date: 2020-11-29
draft: false # 非公開ならtrueにする
---

ここに内容を書く。
1.md
---
title: "Chapter 1-1"
date: 2020-11-29
draft: false
---

ここに内容を書く。

再びサーバーを起動してサイトを確認してみましょう。

Terminal
$ hugo server -D

http://localhost:1313/

スクリーンショット 2020-11-29 21.04.18.png

サイドバーにメニューが表示されました!

デザインを編集したい

サイトのデザインはsample_document/themes/hugo-theme-techdoc/layoutsで管理されています。
編集したい時は、sample_document/layouts以下に同じファイルをコピーして編集します。

すると、上位側のデザインが優先されて表示されます。
テーマのアップデートに備えて、themes配下のファイルは直接編集しないようにしましょう。

ホスティングサービスで公開する

GitHub Pages等で公開する場合、ビルド(HTMLファイルを生成する作業)が必要になります。
NetlifyはHUGOがインストールされており、連携したリポジトリにpushするだけでビルドもしてくれます。

ホスティングの方法に関しては、今回省略します。

Terminal
$ hugo
# public配下に全てのHTMLファイルが生成される
sample_document
└── public
    ├── 404.html
    ├── algolia.json
    ├── categories
    │   ├── index.html
    │   └── index.xml
    ├── chapter1
    │   ├── 1
    │   │   └── index.html
    │   ├── index.html
    │   └── index.xml
    ├── css
    │   ├── chroma.css
    │   ├── chroma.min.css
    │   ├── theme.css
    │   └── theme.min.css
    ├── index.html
    ├── index.xml
    ├── js
    │   └── bundle.js
    ├── sitemap.xml
    └── tags
        ├── index.html
        └── index.xml

おわりに

HUGOは公式テーマが豊富なので、初心者でも扱いやすい静的サイトジェネレーターだと思います!
ただ、設定ファイルの使い方などは、テーマによって大きく異なるので最初は苦戦するかもしれません。

テーマによっては、導入方法や使い方などが丁寧に説明してあるので安心です。
今回使用したテーマのデモサイト↓
https://themes.gohugo.io//theme/hugo-theme-techdoc/

参考

HUGO公式ドキュメント
https://gohugo.io/documentation/
Quick Start
https://gohugo.io/getting-started/quick-start/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goでコールグラフを自作してみた

フューチャー Advent Calendar 2020の8日目の記事です。

普段はJavaの静的解析をすることが多いですが、今回はGoの静的解析に手をつけてみました。本記事ではGoでコールグラフ自作をしてみたいと思います。すでにofabry/go-callvis: Visualize call graph of a Go program using dot (Graphviz)といったコールグラフ作成ツールがありますが、今回はあえてコールグラフを自作して既存のツールを使うだけではできない拡張をしてみました。

なお私はGoはあまり書いたことがないのでツッコミどころは多々あるかと思います。気になる点などございましたらご指摘いただけると幸いです。

シンプルなコールグラフ作成

GoではGoのソースコードの静的解析を行うライブラリが準標準パッケージとして用意されているようで、それを使うだけで簡単にコールグラフを可視化することができました。まずは何も考えずにコールグラフを作成してみました。

今回はisucon/isucon10-qualify: ISUCON10予選のソースコードを対象にコールグラフを作成してみましょう。コールグラフは関数をノード、関数呼び出しをエッジとしており、ノードにはパッケージ名.関数名というラベルを、エッジにはfile=ファイル名, Line=行番号というラベルを表示しています。

解析結果 その1(何も考えず全て表示)

下記のようなグラフになりました(オリジナルの画像は大きすぎるせいかQiitaにアップロードできなかったので縮小しています)
解析に使用したソースコードは解析結果 その1です。

graph_base.png

あまりにも巨大で何が何だかよくわかりませんね。グラフを見てみると、ISUCONではそんなに重要でないログ出力などが多数含まれています。まずは特に重要なCRUDに注目したいということで、思い切ってcalleeがsql, sqlx以外のpackageへ繋がるエッジは除外してみましょう。

解析結果 その2(sql,sqlx以外のエッジを除去)

これくらいなら全容が把握できそうと思える規模になってきました。
解析に使用したソースコードは解析結果 その2です。

graph_crud.png

これを見ればどの関数でCRUDが行われているか把握できそうですね。
ただし、これだけではどのエンドポイントからどのCRUDが行われているかわからないので今度はエンドポイントを表すノードを追加してみましょう。

解析結果 その3(エンドポイントのノードを追加)

どのエンドポイントでCRUDが行われているかわかるようになりました。本当はどのテーブルのどのカラムにアクセスしているかといった情報も載せたかったのですが、そこまでは手が回りませんでした。
解析に使用したソースコードは解析結果 その3です。
(わかりやすいようにエンドポイントはlightgreenで着色しています)
graph_add_endpoint.png

まとめ

Goの準標準パッケージがとても充実しており、サクッと静的解析できて素晴らしいなと感動しました。

単にコールグラフを作るだけなら既存のツール1を使えば済みますが、自作することで自由にカスタマイズすることができ、知りたいこと、伝えたいことにフォーカスしたコールグラフが作成できそうですね。

また今回のようにCRUDだけに注目するのではなく、開発したアプリをうまく可視化することで後からチームに入ってきた人が設計を理解する資料の一つとして使ったり、あるいは既存のLintでは対応できない項目のチェックができたり2とうまく活用すれば静的解析はとても有用そうだと思いました。

(余談ですがssautilパッケージのAllPackages関数はssautil · pkg.go.devのドキュメントには Load function in LoadAllSyntax mode. と書かれているので LoadAllSyntax という定数でモード指定することを推奨しているような気がするのですが、このpackages · pkg.go.devをみるとLoadAllSyntaxはdeprecatedなんですよね。使うべきなのかそうでないのか…)

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeetCodeに毎日挑戦してみた 53. Maximum Subarray(Python、Go)

はじめに

無料英単語サイトE-tanを運営中の@ishishowです。

プログラマとしての能力を上げるために毎日leetcodeに取り組み、自分なりの解き方を挙げていきたいと思います。

Leetcodeとは

leetcode.com
ソフトウェア開発職のコーディング面接の練習といえばこれらしいです。
合計1500問以上のコーデイング問題が投稿されていて、実際の面接でも同じ問題が出されることは多いらしいとのことです。

golang入門+アルゴリズム脳の強化のためにgoとPythonで解いていこうと思います。(Pythonは弱弱だが経験あり)

12問目(問題53)

53. Maximum Subarray

問題内容

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Follow up: If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.

(日本語訳)

整数配列が与えられた場合nums、合計が最大である連続するサブ配列(少なくとも1つの数値を含む)を見つけて、その合計を返します

フォローアップ:O(n)解決策 を見つけた場合は、分割統治法を使用して別の解決策をコーディングしてみてください。これはより微妙です。

Example 1:

  Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
  Output: 6
  Explanation: [4,-1,2,1] has the largest sum = 6.

Example 2:

  Input: nums = [1]
  Output: 1

Example 3:

  Input: nums = [0]
  Output: 0

Example 4:

  Input: nums = [-1]
  Output: -1

Example 5:

  Input: nums = [-2147483647]
  Output: -2147483647

Constraints:

  • 1 <= nums.length <= 2 * 104
  • -231 <= nums[i] <= 231 - 1

考え方

  1. 配列の一つ前の数字が正か負か判断する

  2. もし正なら、現在の数字にその数字を足す、負ならそのまま

  3. ループ後に変更した配列の中で一番大きい数字を戻り値として返す

  • 解答コード
for i in range(1, len(nums)):
        if nums[i-1] > 0:
            nums[i] += nums[i-1]
    return max(nums)
  • Goでも書いてみます!
func maxSubArray(nums []int) int {
    for i:=1; i<len(nums); i++{
        if nums[i-1] > 0 {
            nums[i] += nums[i-1]
        }
    }
    return max_num(nums)
}

func max_num(nums []int) int {
    max := nums[0]
    for _, num:= range nums {
        if max < num {
            max = num    
        }
    }
    return max
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む