技術的な話題 に関する投稿を表示しています

大きくなってしまった git リポジトリへの対抗策

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

ギョームで使ってるあるシステムのリポジトリが大きすぎて、クローンめっちゃつらい、というかできないんだけど!となったので調べた。

git リポジトリの肥大化は、長年積み重なったログ、うっかりコミットされた大きなファイル、バイナリファイルの積み重ねが主な原因っぽい。
(バイナリファイルは差分でログにしていくのではなく、フルでログになる)

なので、大きく 2 つの方向があると思う。
1 つめはリポジトリ自体のログをねじ曲げる方向。ログが原因なので、そのログから原因要素を取り除けばいいじゃん!という理解。
もう 1 つはクローンするときに工夫する方向。ログが大きすぎるのでログを最小限にしていく。

方法 1 : ログを書き換える

方法 1-a : リポジトリを作り直す

リポジトリのログをねじ曲げるにあたって、手っ取り早い改善策はリポジトリを作り直すこと。最新の状態を手元に持っておき、リポジトリを捨て、最新の状態でファーストコミットをする。もろもろの再設定の手間をかけられる、ログや Github などのリポジトリに紐づく情報はポイしてもよい、そんな決断ができるなら一番簡単で効果も高い方法です。

ただ、これからクローンするのはを楽にしたいんだけど!という用途にはちょっと違うかも。

方法1-b : filter-branch サブコマンドを使って不要なファイルを歴史から隠蔽する

filter-branchを使うと、ログを遡ってコマンドを適用していけます。これを使ってログに潜む悪のファイルを精査します。ただあんまりつかったことないのでわからんね、かなしみ。

Git - 歴史の書き換え

全コミットからのファイルの削除

$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten

こういう使い方で、まあまあ、簡単に捨てられそうですね。

試しに適当なリポジトリで実行してみたら、キレイサッパリとファイルが消え去りました。ちなみにコミットハッシュも変わるようです(そりゃそうだ)

$ git log --oneline --graph
* 94a3acd (HEAD -> master) ...
* c5615d4 ...
* 1b58a6d ...
* 958f4b0 ...
* 6aeccd4 ...
* 2d39eca ...
* b22e882 ...
* 554d930 ...
* d620f96 ...
* d0a5a86 ...

$ git filter-branch -f --tree-filter "rm -f package.json"
Rewrite 94a3acd99880461b586931f4d4a283494d3f9fcc (8/10) (3 seconds passed, remaining 0 predicted)
Ref 'refs/heads/master' was rewritten

$ git log --oneline --graph
* ccd7b67 (HEAD -> master) ...
* 5159356 ...
* 5551da3 ...
* 862c676 ...
* c1f6cb5 ...
* 3f1eb70 ...
* 36bc53c ...
* d7c2fa6 ...
* d620f96 ...
* d0a5a86 ...

ということで、方法1はどちらもログをぶっ壊していくので、コミットハッシュが変わります。自分も含め、既にクローンなどによってリポジトリを追跡してた人は、整合性が取れなくなるので、再度ローカルのリポジトリ状況を刷新する必要がありそうです。 fetch しよう!とかそういう話じゃなくて、過去のログを持ち込まないとか、そういうところ。

方法 2 : クローンするときに工夫する

方法 2-a : Shallow Clone

Shallow Clone の方法の1つとして depth というプロパティが設定でき、ログをどこまで遡ってクローンするかを決められます。


$ git clone --depth ...

これをするとログの遡りが少なくなるので、その分早くクローンをすることができます。かなり大きくなったリポジトリで試したらこれくらいの違いでした。 およそ 1 分かかっていたものが 30 秒程度にまとまって非常にすばらしい。

$ time git clone ...
Initialized empty Git repository in ...
remote: Counting objects: 137514, done.
remote: Compressing objects: 100% (31116/31116), done.
remote: Total 137514 (delta 108680), reused 129419 (delta 102311)
Receiving objects: 100% (137514/137514), 215.85 MiB | 8.55 MiB/s, done.
Resolving deltas: 100% (108680/108680), done.

real    1m1.198s
user    0m19.858s
sys     0m6.843s


$ time git clone --depth 1 ...
Initialized empty Git repository in ...
remote: Counting objects: 10862, done.
remote: Compressing objects: 100% (6934/6934), done.
remote: Total 10862 (delta 4852), reused 8236 (delta 3409)
Receiving objects: 100% (10862/10862), 109.83 MiB | 7.15 MiB/s, done.
Resolving deltas: 100% (4852/4852), done.

real    0m27.406s
user    0m6.650s
sys     0m2.897s

足りないログについては git fetch --depth で値を増やすと補完できるっぽい。

$ git log --oneline | wc -l
1

$ git fetch --depth 10000
remote: Counting objects: 126652, done.
remote: Compressing objects: 100% (22233/22233), done.
remote: Total 126652 (delta 103609), reused 123617 (delta 100851)
Receiving objects: 100% (126652/126652), 106.69 MiB | 5.92 MiB/s, done.
Resolving deltas: 100% (103609/103609), completed with 1886 local objects.

$ git log --oneline | wc -l
13101

Shallow Clone したリポジトリは、そういうリポジトリになるようで、深さを再設定しないと永遠とログが出てこないそうです。ちょっと昔までは push と pull もままならなかったようですが、今は改善されたようです。

いつのまにかGitのshallow cloneが”Push”も"Pull"もできるように超進化していたよ! すごーい! - ブログなんだよもん


とりあえず手元でこのブランチの様子を検証したいんやけど!みたいなものなんかは --depth 1 を積極的に使っていくとよさげたん CI ツールの類もクローンするときは全部これでええやんけ。


そのほか勉強になった記事

[Git] git repository size を削減する | deadwood

巨大なリポジトリ を Git で上手く扱う方法 | Atlassian Blogs

postfix で Gmail に送ると弾かれる件を解消する

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

某所から送信されるメールが GMail で弾かれてしまい、まったく送らていなかったのでその対応メモ…、メモというほどじゃない。エラー文言で調べたら一発だ。

が、メモってたエラー文言をなくしてしまったのと、サーバにもログがない(なんでやねん)ので実際にでてた文言はわからないけど、多分一緒やろ(震え)

Mail delivery to Gmail account fails: certificate verification failed for *.l.google.com – Plesk Help Center

ca-bundle.key を更新、設定したら良いっていってる。

// OpenSSL, ca-bundle.crt を更新する
$ sudo yum update openssl
..


// 設定値の追加
$ vi /etc/postfix/main.cf

smtp_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt


// postfix の再読込
$ sudo service postfix reload

いじょ。

php-amqplib と RabbitMQ について調べた

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

見慣れないライブラリを使ってるプロジェクトがあったので調査メモ。

php-amqplibとは?

php-amqplib/php-amqplib: AMQP library for PHP

RabbitMQ とよばれるメッセージングキューのためのクライアントライブラリ for PHP 。厳密には RabiitMQ という話ではなく、そこで利用されるAMQP と呼ばれるプロトコルに準拠したものを取り扱う。メッセージのやり取りについてその専用のプロトコルでやりとりしている。
利用するには mbstring と bcmath のエクステンションが必須になる。

php-amqplib は packagist で管理されているので composer からインストールすることになる。

RabbitMQ とは?

RabbitMQ - Messaging that just works

うさぎがかわいい。

AMQP というメッセージプロトコルを語れる、メッセージングキュー用のミドルウェア…、という分類でいいのかな。

Javascript の Promise みたいなイメージでいると多分認識が合う(合わない)
メッセージを送る人と受け取る人、そして運び人のウサギがいる。ウサギが様々なメッセージのやり取りや順序管理をしてくれるので、送る人は誰に届くかわからないけどこの処理できる人やって、受け取る方は誰から来たかわからないけど処理して結果をしまっておく、なんてことが出来る。
(=マイクロサービスなアーキテクチャ)

ちなみに Erlang で書かれている。
ちなみに今年で 10 年経つんだとかでトップページがお祝いムード。

 

手軽に試すなら公式イメージがあるので docker で。

library/rabbitmq - Docker Hub

 

そうでなければ公式サイトを参考にインストールしていくとよい。

RabbitMQ - Downloading and Installing RabbitMQ

CentOS の項目をみるとこんなことが書かれている。

Overview
rabbitmq-server is included in Fedora. However, the versions included are often quite old. You will probably get better results installing the .rpm from PackageCloud or Bintray. Check the Fedora package details for which version of the server is available for which versions of the distribution.

Fedora には rabbitmq-server な名前で既に登録されているものがあるが、古い可能性ので Package Cloud の RPM を使うのがオススメする、って書いてあった。

rabbitmq/rabbitmq-server - Packages - packagecloud.io | packagecloud

 

ちなみに、ちょっと昔の記事なので、バージョンが古くて今と異なる部分があるかもだが、実際に業務運用に向けていくようなところも込で、がっつりとした情報があったので、こちらも合わせて読むと実践向けっぽい。

はじめての RabbitMQ|サイバーエージェント 公式エンジニアブログ

PHPから使ってみる

例によってお手軽に試してみる程度。 docker で準備を進めていく。

// docker でキューサーバを立ち上げてみる
// 5672 ポートをバインドするのを忘れずに
$ sudo docker run -d -p 5672:5672  --name rabbit-server rabbitmq:3
...

$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                                                   NAMES
92547dbde579        rabbitmq:3          "docker-entrypoint.s   5 seconds ago       Up 4 seconds        4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, 25672/tcp   rabbit-server


// モジュールが必要なので入れる(mbstringはもともとあった)
$ sudo yum install php-bcmath --enablerepo=remi-php70

// モジュールを移動しないとダメだった(パス設定してないだけだと思う)
$ sudo mv /etc/php.d/20-bcmath.ini /etc/opt/remi/php70/php.d/
$ sudo mv /usr/lib64/php/modules/bcmath.so /opt/remi/php70/root/usr/lib64/php/modules/

$ php -m | grep bcmath
bcmath


// アプリを構築する
$ mkdir php-amqplib-test
$ cd php-amqplib-test

$ composer init
...

$ composer require php-amqplib/php-amqplib:2.7.*
...

ソースを書く。

<?php
// send.php

require('vendor/autoload.php');

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

// デフォルトID/PW = guest/guest
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');

// 送信するチャンネルを設定
$channel = $connection->channel();

// https://github.com/php-amqplib/php-amqplib/blob/master/PhpAmqpLib/Channel/AMQPChannel.php#L597
// string queue = ''
// bool passive = false
// bool durable = false
// bool exclusive = false
// bool auto_delete = true
// bool nowait = false
$channel->queue_declare('hello', false, false, false, false);


// メッセージ作成
$msg = new AMQPMessage('Hello World!');

// メッセージ送信
// https://github.com/php-amqplib/php-amqplib/blob/master/PhpAmqpLib/Channel/AMQPChannel.php#L1086
// string msg
// string exchange = ''
// string routing_key = ''
$channel->basic_publish($msg, '', 'hello');
<?php
// receive.php

require('vendor/autoload.php');

use PhpAmqpLib\Connection\AMQPStreamConnection;

// デフォルトID/PW = guest/guest
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');

// チャンネルを設定
$channel = $connection->channel();

// https://github.com/php-amqplib/php-amqplib/blob/master/PhpAmqpLib/Channel/AMQPChannel.php#L597
// string queue = ''
// bool passive = false
// bool durable = false
// bool exclusive = false
// bool auto_delete = true
// bool nowait = false
$channel->queue_declare('hello', false, false, false, false);


// コールバック関数の用意
$callback = function ($msg) {
        echo $msg->body . "\n";
};

// メッセージ受信
// https://github.com/php-amqplib/php-amqplib/blob/master/PhpAmqpLib/Channel/AMQPChannel.php#L901
// string queue = ''
// string consumer_tag = ''
// bool no_local = false
// bool no_ack = false
// bool exclusive = false
// bool nowait = false
// function callback = null
$channel->basic_consume('hello', '', false, true, false, false, $callback);

// メッセージ受信待ちループ
while(count($channel->callbacks)) {
    $channel->wait();
}

なお引数に関するコメントはデフォルトの値。これが何を意味しているかはまたちょっと分からない…。ドキュメントを読むしか…。

ちなみに PHP の API ドキュメントは見当たらない。 Github のソースを読むか、インタフェースはだいたい同じなので JavaDoc のものを読むかするとよい。

 

ターミナルを2つ開いて実行してみる。

// 一方のターミナル
$ php send.php
$ php send.php
$ php send.php
$ php send.php

// もう一方のターミナル
$ php receive.php
Hello World!
Hello World!
Hello World!
Hello World!

ただし、これには問題があって、このように単純に送るだけだとキューが RabbitMQ を再起動したりマシンが落ちたりすると、中身がきれいさっぱり消える。

$ php send.php
$ sudo docker restart rabbit-server
$ php receive.php

// でてこない!

公式ドキュメントだとこのページの Message durability の項目に書いてあるが、送信時に引数を設定しないといけない。

RabbitMQ - RabbitMQ tutorial - Work Queues

<?php
require('vendor/autoload.php');

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

// デフォルトID/PW = guest/guest
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');

// 送信するチャンネルを設定
$channel = $connection->channel();

// https://github.com/php-amqplib/php-amqplib/blob/master/PhpAmqpLib/Channel/AMQPChannel.php#L597
// string queue = ''
// bool passive = false
// bool durable = false
// bool exclusive = false
// bool auto_delete = true
// bool nowait = false
$channel->queue_declare('hello', false, true, false, false);


// メッセージ作成
// デリバリモードの追加
$msg = new AMQPMessage('Hello World!', ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]);

// メッセージ送信
// https://github.com/php-amqplib/php-amqplib/blob/master/PhpAmqpLib/Channel/AMQPChannel.php#L1086
// string msg
// string exchange = ''
// string routing_key = ''
$channel->basic_publish($msg, '', 'hello');
// 一旦再起動しておく
$ sudo docker restart rabbit-server

// 送信, 再起動, 受信
$ php send.php
$ sudo docker restart rabbit-server
$ php receive.php
Hello World!

なるほどね~。

ちなみにの話

ちなみに Laravel のキュードライバとして RabbitMQ を使うライブラリも既にあり、これはもう入れるだけでいい。先人の知恵すごい。

vyuldashev/laravel-queue-rabbitmq: RabbitMQ driver for Laravel Queue

書いてある通りです、以上の説明は出来なくて。要するに QUEUE_DRIVER を変更し、 RabbitMQ 用の設定を幾つか書けば勝手にやってくれるのだそうだ。

ちなみに上記の durable 、メッセージの永続化はデフォルトで有効になっているようだ。

 

ちなみにちなみに、メッセージキューの実装を標準化しよう!という話があるっぽい。知らなかった。動きとしては将来的に PSR になったらいいな、くらいの強制していないけどぼちぼち運動してます!なもの。

queue-interop/queue-interop: Promoting the interoperability of MQs objects. Based on Java JMS

書き方、インタフェースが大きく分かれそうだし、複数対応を謳うようなライブラリもあるし、標準化されるといろいろ嬉しい予感がする。

残念ながら ISUCON 7 は予選敗退で幕を閉じた

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

ISUCON 5, ISUCON 6, と引き続き ISUCON 7 に参加しました。

ISUCON についてはこのあたりを見ると良いです。
ISUCON7 まとめ : ISUCON公式Blog

今回もぼっちーむを回避して、会社の人と組んでいきました。前回の ISUCON 6 と同じメンバーです。

過去の参戦レポはこのあたり参照するとよいです。

今回は、ぼくの参戦時間が実質 3 時間程度、少ない時間しか当てられませんでした。
(ぼくからみた義親、つまりは妻の両親が家庭訪問という重大なイベントが発生して、椅子コンよりも優先度高めだった)

当日までにやっておいたこと

メンバーで集まって、何度か方針や準備するものについて話あっていました。準備するのは当日にあたふたしないように、ある程度は機械的に出来るように、というところからです。
具体的にはこんなものが挙がりました。

  • さくらクラウドのアカウント準備
    • クーポン投入
    • お金をケチらない気持ち。必要なら大規模マシンで殴って戻すことも視野に。
  • 個々人の環境
    • ISUCONイメージがさくらクラウドから提供される?情報まち。
    • されないならubuntu使って空っぽマシン用意
      • 中身をがばっともってくる
  • 簡単に色々なログ仕込める君
    • スクリプトとかansible的なやつでやりたいよね
    • mysql-slowとかアクセスログとか。
    • CPUとかメモリ、ネットワークの様子もみたいよね
    • プロファイリングツールの準備
      • alp
      • new relicをいれたい
        • 準備だけしておきたい
        • PHPの計測もできるが細かに計測する記述をしないといけない
      • Kibana
        • mysql も
        • td-agent でいろいろ飛ばせば全部見れる
  • ssh公開鍵の準備
    • 鍵をcurlするだけでつなげるような状態にしたい
  • gitリポジトリ作る
    • 鍵とssh-configとを準備する
  • つけるべきHTTPヘッダの整理
    • 304 not modified, expire + etag
    • 200 ok
    • 301 / 302 転送
  • 画像圧縮
    • ツールや構築の下準備
  • ご飯@ランチの用意
    • 当日朝各自で買ってこよう
    • 大人なのでお菓子とアルコールはご自由に!
  • FWになれる
  • 実装の読み解き
    • 頭の中でやるとつらい
    • ホワイトボードに書こう
    • 付箋を使おう
  • 当日の流れを作っておく

ISUCON のたびに毎回考えるのはアレなので、そろそろメンテしながら使いまわしをしていきたい所存。

それと、練習しよっ♡、なんて言ってたけど結局一回もやってない。いや、環境構築までは一回やった。 vagrant-isucon という素晴らしいものがあって、これを使わせてもらった。当時行われた時の得点まで伸びはしないだろうが、何をしたらどう伸びるのか、という部分は見ていける、便利ちゃん。

matsuu/vagrant-isucon: ISUCON過去問を構築するためのVagrantfile集

ちなみに結局当日になっても、このリストの大半は実施あるいは準備されませんでした。。熱量の違いだったり、そういうアレ。

当日にやったこと

合流したのは18時過ぎ。それまでに以下のことをチームメンバーが進めていました。

  • phpへの切り替え
  • リポジトリの準備
  • /messages/ の N+1 の解消
  • インデックス設定
  • 画像ファイルを DB から取り出し DB に保存しないようにする
  • nginx, mysql, php-fpm の微調整

合流したときにはスコア的には 1 万くらい?
( っ˘ω˘c).。o○( なんでスコア伸びてないんだ…? )

そこからはこんなことを進めていきました。

  • 画像圧縮するしないで揉めてたっぽいのでやるなって言った
  • それするより画像に 304 つけろって言った
  • LINE 通話をつなげた
  • ハッピーターンでお腹を満たす
  • /fetch の N+1 改善
  • nginx の設定サポート
  • /message や /login あたりの微妙な改善

時間も少なく、現場まで合流するのは厳しかったのでリモートで作業せざるを得ませんでした。チャットだけでも良かったんですが、声が繋がったほうが何かと便利なので LINE 通話を PC で繋ぎっぱなしにして、あーだこーだ言いながらやってました。

最終スコアは 37000 くらい、だったかな?(結果一覧に出ている点数と微妙に違うっぽい?)

ISUCON 5 とくらべて、アプリ上の実装の問題はほとんど消化できたと思っていて、そのあたりは前回からレベルアップしたと思う(チームとして)

画像の304については理解が浅くて結局出来ていなかった。周りの攻略記事を見ると、ここが出来る出来ないでスコアが大きく変わっていたように思える。最後までベンチマーク実行結果に icons のレスポンスが遅くて~、とあって、これが改善できないともうスコア伸びないってのは分かっていたけど、いろいろなものを信用しすぎて何も動けなかった。

結局よくわかんなくて合流してからずっと右往左往したところがあって、ベンチマーク時に 2 つチェックいれたらどうなんねん、というところ。リバースプロキシしてバランシングしたり、外したり、そもそも設定うまく行かなくて、だいぶ時間がかかった。結局 2 つチェックいれたらベンチが倍速になってスコア上がるんかな?どこかに説明あったっけ(見落としたかも?)

サーバ構成的には AP + AP + DB という、多分他と同じ構成。

次に向けて

ISUCON が来年も実施されるかわからないけれども(ものすごく楽しいイベントなので実施されてほしい)、自分のスキルアップはもちのろん、チーム側の意識・熱量・レベル感が合ってないどうすっかなあ、というところをやっていかないと勝てないだろうなって気持ち。例えばあった話だと、とりあえず画像圧縮してプロキシキャッシュすれば勝てるやろ~~、とか真面目に雑な話をしている人とか。19時くらいになってわかんないからスロークエリ入れるか~~~とか言ってたり、先やれよ。とか。うごかないんだけど見てくれ~って、別にこっちも分からないから都度調べてるんだけどなあ…。とかなんとか、こんな人と仕事していたのかとか云々。余談が過ぎた。ようは 3 人で役割を分けてそれぞれ最高のパフォーマンスをしていくべきで、それは 2 人になっても変わらなくて、ぼくが居ないまたは手が出せない状況だろうと頑張ってくれって気持ち。なのでそれが足りないっぽいのでチームビルディングというか、そういう類の云々がだめだめだったんだろうなあ~~。

もし次があったとして、次も同じチームででるかっていうと怪しく、楽しく全力で戦えるチームで出来るといいな。どんどんやっていこう。
結果として ISUCON 7 は予選敗退だったけれども、俺の、俺たちの、 ISUCON 7 はまだ終わってない!(練習しよ

そんなポエムで〆


ISUCON 運営の人たちメチャ忙しいと思いますけど、こうやって今年も楽しいイベントを開催してくれてありがとうございました?

Windows でも ANSI エスケープした

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

Windows ネイティブに動く php.exe を使って phpunit を動かしていたわけですが、どうにも --color をした時にカラーコードが反映されない。

いやそもそも Windows やんけ、という話は置いておき。

カラーコードが出来ないことで何か問題あるかってーとこれまた特にあるわけでもなく。とはいえ、あたまのいい人たちが何か考えてるでしょと思いながら調べたら解決した。

そもそもとして色が出るのは ANSI エスケープシーケンスに割り当たってるところに色定義があるからっぽい。 ANSI カラーコードとか、 xterm16 とか、そう呼ばれているっぽい。
背景的なところはわからなんだ。

ANSIエスケープコード - コンソール制御 - 碧色工房
ターミナルのANSIカラーの分布 - Folioscope
ターミナル環境のTrue Colorとは?意味やMac、Vimなどの対応状況まとめ | Simplie Post

で、コマンドプロンプトでどうすんねん、についてはこれをつかうと出来る。

adoxa/ansicon: Process ANSI escape sequences for Windows console programs.

ANSICON provides ANSI escape sequences for Windows console programs. It provides much the same functionality as ANSI.SYS does for MS-DOS.

といった、説明のとおり Windows において ANSI エスケープシーケンスを実現するためのツール。
使い方はこんな感じ。

上に書いたリポジトリをダウンロード。適当な場所において、 32 ビット OS なら x86 を。 64 ビット OS なら x64 のフォルダをエクスプローラーで開く。そのフォルダのパスをコピーしておく。

コマンドプロンプトの起動。レジストリ登録をするそうなので管理者権限がよいようにみえるけど…。

フォルダへ移動

ansicon -i の実行

コマンドプロンプトを再起動する。これでカラーコードが使えるようになる。例えばこんな風に。

余談だけど cmder や Cygwin といったターミナルを使っていた場合は、内部でよろしくやっているので、特に何もしなくても出る。

cmder、タブが使えたり Linux のそれっぽく振る舞ってくれたりして便利ちゃんでおすすめちゃん。

cmder | Console Emulator

Cygwin のそれみたいなもっさり感…?はない、と思うけど、Cygwin も昔さわった時にもっさりしてた気がするだけで、今は改善されているのかな…?どちらにしても結局のところ Windows なのはかわりないので、そういうところ気がきかないのね~~~みたいのがたまにあって、そういうときに辛みを感じる。

Bitbucket Pipelines で MySQL コンテナを使う時に早すぎるとエラーになる

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

Bitbucket Pipelines を使ってて、全く設定を変えていないのに MySQL に繋がったり繋がらなくなったりしていたので、どういうこっちゃ、というメモ。

結論から行くと 3 〜 5 秒くらい待ってあげればよい。

MySQL に繋がらない

エラーとしてはこんな感じ。ちなみにこの問題と直接関係はないが CakePHP 3.5 である。

+ bin/cake migrations migrate
using migration paths 
 - /opt/atlassian/pipelines/agent/build/config/Migrations
using seed paths 
 - /opt/atlassian/pipelines/agent/build/config/Seeds
Exception: There was a problem connecting to the database: SQLSTATE[HY000] [2002] Connection refused in [/opt/atlassian/pipelines/agent/build/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php, line 115]
2017-10-16 10:57:41 Error: [InvalidArgumentException] There was a problem connecting to the database: SQLSTATE[HY000] [2002] Connection refused in /opt/atlassian/pipelines/agent/build/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/MysqlAdapter.php on line 115
Stack Trace:
#0 /opt/atlassian/pipelines/agent/build/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php(238): Phinx\Db\Adapter\MysqlAdapter->connect()
#1 /opt/atlassian/pipelines/agent/build/vendor/cakephp/migrations/src/CakeAdapter.php(57): Phinx\Db\Adapter\PdoAdapter->getConnection()
#2 /opt/atlassian/pipelines/agent/build/vendor/cakephp/migrations/src/Command/CommandTrait.php(78): Migrations\CakeAdapter->__construct(Object(Phinx\Db\Adapter\MysqlAdapter), Object(Cake\Database\Connection))
#3 /opt/atlassian/pipelines/agent/build/vendor/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php(72): Migrations\Command\Migrate->bootstrap(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#4 /opt/atlassian/pipelines/agent/build/vendor/cakephp/migrations/src/Command/CommandTrait.php(35): Phinx\Console\Command\Migrate->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#5 /opt/atlassian/pipelines/agent/build/vendor/cakephp/migrations/src/Command/Migrate.php(65): Migrations\Command\Migrate->parentExecute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#6 /opt/atlassian/pipelines/agent/build/vendor/symfony/console/Command/Command.php(262): Migrations\Command\Migrate->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#7 /opt/atlassian/pipelines/agent/build/vendor/symfony/console/Application.php(888): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#8 /opt/atlassian/pipelines/agent/build/vendor/symfony/console/Application.php(224): Symfony\Component\Console\Application->doRunCommand(Object(Migrations\Command\Migrate), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#9 /opt/atlassian/pipelines/agent/build/vendor/symfony/console/Application.php(125): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#10 /opt/atlassian/pipelines/agent/build/vendor/cakephp/migrations/src/Shell/MigrationsShell.php(101): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#11 /opt/atlassian/pipelines/agent/build/vendor/cakephp/cakephp/src/Console/Shell.php(508): Migrations\Shell\MigrationsShell->main('migrations', 'migrate')
#12 /opt/atlassian/pipelines/agent/build/vendor/cakephp/migrations/src/Shell/MigrationsShell.php(156): Cake\Console\Shell->runCommand(Array, true, Array)
#13 /opt/atlassian/pipelines/agent/build/vendor/cakephp/cakephp/src/Console/CommandRunner.php(141): Migrations\Shell\MigrationsShell->runCommand(Array, true)
#14 /opt/atlassian/pipelines/agent/build/bin/cake.php(12): Cake\Console\CommandRunner->run(Array)
#15 {main}
script:
	- composer install --no-interaction
	- chmod +x bin/cake
	- chmod -R 777 tmp/ logs/
	- bin/cake migrations migrate
	- composer test

bitbucket-pipelines.yml に記載した実行スクリプトに関してはこれだけなのだが、マイグレーションのところで、トピックブランチでは通って、マスターブランチでは通らず、なんてことがぼちぼち起きた。

Bitbucket Pipelines 上で MySQL のログが見えるので、これを確認すると、通らなかったときには、通ったときのログが途中できれているようなものだった。

Initializing database
...(中略)...

2017-10-16T01:57:41.831583Z 0 [Note] End of list of non-natively partitioned tables

成功した時はこんな感じの最終行になる。

2017-10-16T01:51:55.506134Z 0 [Note] InnoDB: Starting shutdown...
2017-10-16T01:51:55.606342Z 0 [Note] InnoDB: Dumping buffer pool(s) to /var/lib/mysql/ib_buffer_pool
2017-10-16T01:51:55.606688Z 0 [Note] InnoDB: Buffer pool(s) dump completed at 171016  1:51:55

つまり MySQL コンテナが何かしらの初期化処理をしていて、にも関わらず繋ごうとしたから接続できないエラー?そうして Bitbucket Pipelines がエラーを認識して停止。ふむ、納得出来る気がする。

アプリケーション側で接続時のタイムアウト設定

とりあえず思いつくスマートな方法はこれ。

このプロジェクトは CakePHP3 を使っているが CakePHP3 には接続時のタイムアウト設定な項目はないらしい。かなしい。とはいえ何かあるだろうとソースを読み進めると 最終的に PDO を使っているらしいことがわかる。ので PDO の設定方法を調べてみると PDO::ATTR_TIMEOUT というオプションを設定するとよいらしい。

php - Setting a connect timeout with PDO - Stack Overflow

このオプションを CakePHP3 で設定するには、コンフィグ内の flags にいれると出来そうなソースをしている。

cakephp/PDODriverTrait.php at master · cakephp/cakephp

$connection = new PDO(
	$dsn,
	$config['username'],
	$config['password'],
	$config['flags']
);

しかし入れても状況は改善されなかったので、違うらしい。。これはまたそのうち調べよう。。。

bitbucket-pipelines.yml に sleep を入れる

というわけでこっちの方法。yml (の抜粋)はこんな感じ。

script:
	- composer install --no-interaction
	- chmod +x bin/cake
	- chmod -R 777 tmp/ logs/
	- sleep 5
	- bin/cake migrations migrate
	- composer test

これで安定的に動いてくれているので、このままでいいか。

MySQL コンテナの初期化処理

そもそもとして MySQL コンテナの初期化処理って何をしているんだろうか。Dockerfile はここ。どうやら docker-entrypoint.sh が何かしていそうだ。

mysql/Dockerfile at master · docker-library/mysql
mysql/docker-entrypoint.sh at master · docker-library/mysql

すごいざっくり読むとこんなことをしているっぽい。

  • Docker イメージが提供するのは mysqld の準備~起動、 docker-entrypoint.sh の起動まで
  • docker-entrypoint.sh によって初回起動によるデータファイル構築。設定したデータベース、ユーザの準備。ちなみに途中で mysqld の再起動をしている。

なるほど理解。
やっぱりコンテナが立ち上がったと同時に初期化処理が走って、それが終わるまでは接続が出来ないようだ。

ものすごいバッドプラクティスを感じている sleep 5 なのでなんとかしたいものの、どうこうするのがいいのかイマイチわからないので、知見あるひと教えてほしス

WordPress にプラグインを使わずに JavaScript だけで目次機能をつけてみた

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

目次機能が出来るプラグインとかってあるけど、別に自分で作ってもいいよねって思ったので作った。そのうちモリモリっとテーマやプラグインの整備に力を入れていくので、そのあたりに介入されると困るので~~ってくらいの理由で作った次第。

これこれ!この目次! h2 タグを使うんだよ

2階層までなら対応するよ! h3 タグだよ

ここは h3 タグ

ここは h2 タグなので 1 階層目になる

ここは 2 階層。 h3 タグだからね

デモおわり

ここまで見出しだらけで見にくいのは仕方ない。

仕組み

  1. h2 タグと h3 タグを投稿箇所から探す
  2. それぞれのタグに ID をつける
  3. それぞれへのリンクを生成する
  4. 目次用の HTML を作る
  5. 一番最初の見出しの直前に HTML を挿入する

タイトルこそ WordPress って言ってるけど JavaScript が差し込めるブログサービス的なものなら、投稿箇所を探す部分を調整する必要はあるけど、どれでもできるんじゃないかな。
jQuery を使わなくていいように作ったので jQuery ないんだけど、みたいな環境でもいいと思う。いやそもそもそういう環境はまた違う問題っぽいけど…。

JavaScript

(function() {
	setTimeout(function() {
		var headingIndex = 1;
		Array.prototype.forEach.call(document.querySelectorAll('.post_content'), function(postContent) {
			if (postContent.innerText.length < 100) {
				return;
			}

			var headings = postContent.querySelectorAll('h2,h3');
			if (headings.length <= 0) {
				return;
			}

			var indexHtml = '
    '; var lastElement = 'H2'; Array.prototype.forEach.call(headings, function(e) { var id = 'heading-' + headingIndex; e.id = id; if (lastElement != e.tagName) { if (lastElement == 'H2') { indexHtml += '
      '; } else { indexHtml += '
    '; } lastElement = e.tagName; } indexHtml += '
  • ' + e.innerHTML + '
  • '; headingIndex++; }); indexHtml += '
'; var wrapper = document.createElement('div'); wrapper.classList.add('content-index'); wrapper.innerHTML = indexHtml; headings[0].parentNode.insertBefore(wrapper, headings[0]); }); if (location.hash.length > 1) { setTimeout(function() { var element = document.querySelector(location.hash); if (element != null) { window.scroll(0, element.offsetTop); } }, 10); } }, 10); })();

CSS

.post_content .content-index {
	border: 2px solid #eee;
	border-radius: 20px;
	padding: 20px;
	background-color: #fefefe;
}

.post_content .content-index:before {
	content: '目次';
	display: block;
	font-size: 24.5px;
	margin-bottom: 15px;
}

他のスタイルとのバッティング回避した部分を除くとこれだけ。シンプルに線を引くことと色味の調整くらい。

SEO的な観点とか

目次足すと SEO に効果あるのかわからんけど、ふつうに見ていて、長めの投稿だったら合ったほうが便利だよね、とは感じる。

一応 Fetch as Google をして Google Bot にこの目次が認識されていることは確認したので、まあいいんじゃないかな。

composer create-project は何が起こるのか

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

CakePHP 3 のクイックスタートを改めて見てたら composer create-project ... ってでてきて何だこいつは…ってなった話。

Quick Start Guide - 3.x

php composer.phar create-project --prefer-dist cakephp/app bookmarker

composer create-project がやること

要はドキュメントにあるので読めって話になる。

Command-line interface / Commands - Composer

You can use Composer to create new projects from an existing package. This is the equivalent of doing a git clone/svn checkout followed by a "composer install" of the vendors.

There are several applications for this:

1. You can deploy application packages.
2. You can check out any package and start developing on patches for example.
3. Projects with multiple developers can use this feature to bootstrap the initial application for development.

To create a new project using Composer you can use the "create-project" command. Pass it a package name, and the directory to create the project in. You can also provide a version as third argument, otherwise the latest version is used.

ざっくりと翻訳してみると、こんなことが書かれているようだ。

既にあるパッケージから新しいプロジェクトを作ります。それは git clone または svn checkout をして composer install を行うのと同等です。

1. アプリケーションパッケージをデプロイできる
2. パッケージをチェックアウトして開発をしていける
3. 新しい開発のため、ブートストラップな初期化を提供ができる

"create-project" コマンドによってそれは実行できる。パッケージ名、プロジェクトのディレクトリ名、そしてパッケージバージョンを指定できる。

CakePHP のドキュメントにあったコマンドを読み解く

php composer.phar create-project --prefer-dist cakephp/app bookmarker

まず指定されているパッケージを見てみる。

cakephp/app - Packagist

A skeleton for creating applications with CakePHP 3.x.

なるほど。つまり、空っぽの CakePHP アプリケーションがパッケージ登録されているので、それをもってくることで新規に空っぽの CakePHP アプリケーション作れるやん!ということだそうだ。

ところで --prefer-dist はなんだろう。 composer のドキュメントをもう一度見る。

--prefer-dist: Install packages from dist when available.

んーむ。 dist があればインストールするよって言ってるけどなんのことやら…? → install の項目にガッツリと書いてある。

--prefer-dist: Reverse of --prefer-source, Composer will install from dist if possible. This can speed up installs substantially on build servers and other use cases where you typically do not run updates of the vendors. It is also a way to circumvent problems with git if you do not have a proper setup.

--prefer-source の逆で、 dist からのインストールを優先する。これによってビルドサーバやアップデートをしないような環境では高速に動作する。あるいは git の設定をしてないときに問題を解決出来る。

--prefer-source の説明も合わせて読んだところ、どうやら composer は dist って呼ばれるところを基本的には利用するらしい。 source はバージョン管理リポジトリで、直接 git clone するようなものになっているので、設定をしてないと問題が起きたりするそう。 Github を直接見に行ってエラー、とかそういう感じなのかな?まあ、デフォルトでは dist なので --prefer-dist はあまり気にしなくても良さそう。

で。

3 つめの引数に書いてある bookmarker が作られるディレクトリだ。
カレントディレクトリに bookmarker というディレクトリが作られ、その中に cakephp/app パッケージの内容が展開されているはず。加えて composer install も行われているので bookmarker/vendor ディレクトリも作られている。

まとめ

要は git clone しまっせ、みたいなものだと思っておけばだいたい良さそうだ。

Dockerfile を Github で書いて Docker Hub へ自動で連携する

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

出来ると思うんだけど~~って言いながら、なかなかやってなかったのでいい加減やった。

Docker Hub にアカウント登録

Docker Hub

右上からサインアップ出来る。適当な ID とメールアドレス、パスワードを入力すると良い。メールで確認がやってくるので、リンクぽちー。

以上で終わり。

Github でリポジトリを作る

Github にリポジトリを作っていきます。

Github 上からファイルの追加が出来るので、手元の Dockerfile をアップする。当然ながらパブリックなリポジトリで扱うのであれば、社外秘です!みたいな内容を含まないように気をつけて。パスワードとか、脆弱性ある古いバージョン使ってるじゃんとか、鍵が載ってるよとか、うっかり事故気をつけようね。

というわけで出来ました。

sters/docker-php7-cakephp3-base: docker base image for php7 x cakephp3

Docker Hub で Automated Build の設定をする

Docker Hub 側で自動ビルドが出来ます(便利!)URL が提供されるので、正しいトークンを送るとビルドキューへ追加され、自動でビルド、イメージの提供がされ始めます。
というわけで連携を進めていきます。

この URL および トークンを Github の Webhook に追加します。

ちなみに Docker Hub に Webhook もありますが、これはビルドが終わったときにそのイベントを指定した URL に通知できるものです。でっかいイメージを扱いたい時とか、ビルドしたイメージがちゃんと使えるかテストしたいとか、そういうときに使えるんじゃないですかね。

Github で Webhook の設定

Docker Hub で取得できたトリガーの URL を Github の Webhook に設定します。

とりあえずは Push された時、でいいと思います。タグが付いたとき、にすると Docker Hub 側でタグごとのビルドがされていきます。バージョニングしていく上ではそうするほうがいいでしょうね~。

Docker Hub で確認

ビルドが動き始めました。しばらく待つと完了します。

何事もなければこれでイメージが利用できるようになります。

 $ docker run -it --rm sters/php7-cakephp3-base "/bin/sh"

// ここからゲスト側

/ # composer
Do not run Composer as root/super user! See https://getcomposer.org/root for details
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.5.2 2017-09-11 16:59:25

Usage:
  command [options] [arguments]

Options:
  -h, --help                     Display this help message
  -q, --quiet                    Do not output any message
  -V, --version                  Display this application version
      --ansi                     Force ANSI output
      --no-ansi                  Disable ANSI output
  -n, --no-interaction           Do not ask any interactive question
      --profile                  Display timing and memory usage information
      --no-plugins               Whether to disable plugins.
  -d, --working-dir=WORKING-DIR  If specified, use the given directory as working directory.
  -v|vv|vvv, --verbose           Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  about           Shows the short information about Composer.
  archive         Creates an archive of this composer package.
  browse          Opens the package's repository URL or homepage in your browser.
  clear-cache     Clears composer's internal package cache.
  clearcache      Clears composer's internal package cache.
  config          Sets config options.
  create-project  Creates new project from a package into given directory.
  depends         Shows which packages cause the given package to be installed.
  diagnose        Diagnoses the system to identify common errors.
  dump-autoload   Dumps the autoloader.
  dumpautoload    Dumps the autoloader.
  exec            Executes a vendored binary/script.
  global          Allows running commands in the global composer dir ($COMPOSER_HOME).
  help            Displays help for a command
  home            Opens the package's repository URL or homepage in your browser.
  info            Shows information about packages.
  init            Creates a basic composer.json file in current directory.
  install         Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.
  licenses        Shows information about licenses of dependencies.
  list            Lists commands
  outdated        Shows a list of installed packages that have updates available, including their latest version.
  prohibits       Shows which packages prevent the given package from being installed.
  remove          Removes a package from the require or require-dev.
  require         Adds required packages to your composer.json and installs them.
  run-script      Runs the scripts defined in composer.json.
  search          Searches for packages.
  self-update     Updates composer.phar to the latest version.
  selfupdate      Updates composer.phar to the latest version.
  show            Shows information about packages.
  status          Shows a list of locally modified packages.
  suggests        Shows package suggestions.
  update          Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.
  upgrade         Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.
  validate        Validates a composer.json and composer.lock.
  why             Shows which packages cause the given package to be installed.
  why-not         Shows which packages prevent the given package from being installed.

/ # php -v
PHP 7.1.10 (cli) (built: Sep 30 2017 01:01:44) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies

/ # exit

おわり

基本的に Official なイメージを使うとおおよそ大丈夫だと思うけど、再ビルド必要です!とか拡張機能必要です!とか、そういう状況になったら都度やるのもアレなので、オレオレイメージを作ると便利ちゃん。

そういえば Docker Hub って Dcokerfile によって記述された Docker イメージを管理共有してくれているけど、 docker-compose.yml で記述されるコンテナ管理セットを共有する場ってあるのかな。ちょっと調べたけどクエリが悪いのか見つからず…。

Office365 の Excel で Ctrl + H を押しても「検索」開く(解決済み)

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

会社の PC で Office365 の Excel を使っているがここ最近で、置換のショートカットキーが効かなくなったような気がしている。

Ctrl + H を押しても置換ではなく、検索が開くのだ。まあ、置換タブを押せばいいだけなのだが。

で気になって調べた。

もしやショートカットキーが変わった?ということもあるので、公式のガイドを見る。

Windows 用 Excel キーボード ショートカットとファンクション キー - Excel

CTRL + H
[検索と置換] ダイアログ ボックスの [置換] タブを表示します。

間違いなく Ctrl + H だ。
特にアプリ追加したなどないし、再起動しても変わらない。はてさて。

日本語の情報が全く見つからなかったが、英語で同じような状況を検索すると同じ状況の困った!がヒット。

Excel Shortcut Keys ctrl+f and ctrl+h now gives the same function? - Microsoft Tech Community

I just want to know why would microsoft change the function of excel 2016 in pressing ctrl+h (Find and replace) and have it the same as pressing ctrl+f (Find fucntion)?? The recent update makes the shortcut keys the same. I mean it is really useful when you can a quickly press keys to find and replace. Instead they made it the same as find function only which really is really annoying and time consuming for excel advanced users.

公式コミュニティだ。そして全く同じ問題が起きている。
雑に訳すと「Excel 2016 を使ってるんだけど Ctrl + H の動きが Ctrl + F と一緒じゃね?」って言ってる。

ついている回答を挙げてみるとこんな感じ。

I updated couple of minutes ago on 1709.8528.2084, Ctrl+H works now in that build.
And I'm on Inseders Slow (now Monthly Channel if i remember correctly)

これまた雑に訳すと「アップデートしたら動くようになったよ!Insider の Slow チャンネルでは直っているからそのうちになおるかも?」みたいなことを言っている。ぼくは大きなダメージもないのでまったり待ち。

 

 

と思ったけど 10/06 の朝に更新をしてみたら治った。
更新は ファイル → アカウント を押して「 Office 更新プログラム」かアップデート確認・実行ができる。

ぼくの場合はこのタイミングで 1708.8431.2094 というビルド番号になり Ctrl + H が直ってた。

1 2 3 4 5 6 7 8 9 10 11 12 13