xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

Azure Functions SDK の更新に伴う FunctionsStartup の書き方の変更

2020/9/16 に Microsoft.Azure.Functions.Extensions パッケージが v1.1.0 がリリースされました。これに伴い、Azure Functions で DI を利用するときに書くことになる FunctionsStartup にアプリケーションの構成情報ソースをカスタマイズするためのオーバーライド (ConfigureAppConfiguration) が追加されました。

実装サンプル

最近は Azure App Configuration が便利過ぎるのでよく利用するのですが、そういうのを読み込み元として追加するときに利用できるでしょう。実装イメージはこんな感じ。

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;

[assembly: FunctionsStartup(typeof(AzureFunctionsSample.Startup))]

namespace AzureFunctionsSample
{
    internal class Startup : FunctionsStartup
    {
        // 新たに追加されたオーバーライド
        public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
        {
            // 既定の構成を取得
            base.ConfigureAppConfiguration(builder);
            var defaultConfig = builder.ConfigurationBuilder.Build();

            // App Configuration を読み込む
            builder.ConfigurationBuilder.AddAzureAppConfiguration(o =>
            {
                var connectionString = defaultConfig["AppConfig:ConnectionString"];
                o.Connect(connectionString);
                o.Select("*");
            });
        }

        // 前からあったオーバーライド
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var services = builder.Services;
            var config = builder.GetContext().Configuration;
            // DI 登録とかとか
        }
    }
}

これまでは Configure メソッドだけですべてやる必要があったのですが、タイミングを切り離すことができるようになった感じでしょうか。

呼び出し順序

試してみたところ、以下の順で呼び出されました。

  1. ConfigureAppConfiguration
  2. Configure

注意事項

Consumption Plan か Premium Plan の場合、スケールコントローラーの関係でアプリケーション構成の値が変更は許可されていない旨の記述がありました。実行時エラーになりそうなので、気を付ける必要がありそうです。

Microsoft 365 (旧 Office 365) のアプリを個別インストールする

f:id:xin9le:20200831010352p:plain

昔 Office のセットアップをしようとしたらアプリごとにインストールするかどうかを選択できました。しかし Microsoft 365 (= Office 365 からリブランドされた) のセットアップを叩いても、インストールしたいアプリを選択できません。

つまりどうなるかと言うと、こんなにたくさんのアプリが一気にドカッとインストールされます。問答無用。

  • Excel
  • Word
  • PowerPoint
  • Outlook
  • Access
  • Groove
  • Lync (= Skype for Business)
  • OneDrive
  • OneNote
  • Publisher

正直全くいらん。使うのは Excel / Word / PowerPoint だけです。少なくとも僕は。ということで、個別にアプリを選択してインストールしたい需要が非常に高いです。

Office 展開ツールを取得

なんとかして消しましょう。消すには Office Deployment Tool というものが必要です。ODT って言う略記が存在するほど高頻度で使われるものとは思えないけど、業界では ODT って言うらしいです。とにかくダウンロードして自己解凍 exe を実行し、setup.exe を取り出しましょう。

f:id:xin9le:20200831004755p:plain

構成オプションファイルを作成

次に、除外するアプリを選択するために構成ファイルを準備します。僕の場合は Excel / Word / PowerPoint 以外不要なので以下のような感じで、除外リストを記述します。

<Configuration>
    <Add OfficeClientEdition="64">
        <Product ID="O365ProPlusRetail" Channel="Current">
            <Language ID="ja-jp"/>
            <ExcludeApp ID="Access"/>
            <ExcludeApp ID="Groove"/>
            <ExcludeApp ID="Lync"/>
            <ExcludeApp ID="OneDrive"/>
            <ExcludeApp ID="OneNote"/>
            <ExcludeApp ID="Outlook"/>
            <ExcludeApp ID="Publisher"/>
        </Product>
    </Add>
</Configuration>

構成オプションはアレコレ記述できるので、公式ドキュメントを参考するとよいかと思います。

セットアップを実行

上で準備した構成オプションファイルを Office Deployment Tool に食わせて実行しましょう。コマンドは以下のような感じで引数を与えて実行するだけです。

.\setup.exe /configure MyConfig.xml

これで不要なアプリたちとはオサラバ!平和になれましたね!

Azure Functions の TimerTrigger の処理時間を可視化する

最近、業務でバッチ処理の最適化を行っています。弊社のバッチ処理は Azure Functions の TimerTrigger で書くことが多いです。主な理由は、仮想マシンなどを利用することなくマネージドな環境でタイマーを発動できて丁度よいという点ですが、Consumption Plan (= 従量課金プラン) を利用していると非常に安価に済むというのもあります。

なのですが、Consumption Plan だと実行時間の上限が 10 分と決まっています。これが結構曲者で、タイムアウトを避けるためには処理時間を短くする努力する必要が出てきます。もしそれでも超過してしまう場合は Premium Plan か App Service Plan を選択することになりますが、できる限り 10 分以内に抑える努力をして Consumption Plan を利用したいものです。

なぜこんなことを言っているかと言うと、ちょくちょくタイムアウトが発生していて困っていたからです...(だいぶ直した

可視化に利用する KQL

パフォーマンス改善を行うに際してはまず状況の把握が大事ということで、可視化を図ります。複数あるバッチ (TimerTrigger な Function) の中でも特にどれがインパクトを与えているのかを調査し、改善に繋げます。今回は Azure Functions のログを Application Insights に送り込むようにしている前提で、Application Insights のログを KQL を使って可視化してみました。

requests
| where customDimensions.TriggerReason startswith 'Timer fired at'
| project timestamp, name, duration
| render scatterchart

このクエリを使って実際に運用している環境のログを取り出してみると、以下のようになりました。Functions の開始時間と処理時間の関係がひと目で分かりますね。

f:id:xin9le:20200412232757p:plain

上図によると概ね 5 秒 ~ 15 秒程度で処理が完了していますが、ものによっては 250 秒近くかかっていると分かります。これらに改善の余地があるかを確認すると良いでしょう。他にも

  • 処理の実行を夜間にすることが望ましいか
  • 期待した頻度/回数で実行されているか (= 無駄に実行されていないか)

などの把握や判断にも繋がるでしょう。先の KQL はどの環境でもコピペで使えるはずなので、ぜひお試しください。

XSitemaps - SEO のためのサイトマップファイルを作る

業務で (主に) EC サイトの実装 / 運営をしているのですが、EC サイトと言えば SEO!お客様が来ないことには商売にならないので、検索エンジンを上手に操って流入を確保することは非常に重要です。ということで、ページのクローリングを制御するために Google Search Console などを使うわけですが、そこで必要になるのがサイトマップファイルです。サイトマップファイルにはいくつか種類があるのですが、主に XML 形式が使われるのだと思います。ファイル仕様は sitemaps.org をご覧ください。

f:id:xin9le:20200112015442j:plain

サイトマップファイルを生成するライブラリは C# / .NET の世界にもいくつか転がっているのですが、どれも微妙に気が利いてなく、社内でもプロジェクト毎に独自に実装を持っていたりして自分的に全く気に入らなかったので、せっかくなのでと思って車輪の再発明をしました。それが今回ご紹介する XSitemaps です。.NET Standard 1.1 以上をサポートしているので、.NET Framework 4.5 でも利用できます。ただ XML ファイルを作るだけとあって幅広い。

主な機能

以下は主にサポートしている機能の一覧です。見る人が見ると案外痒い所に手が届くようになっている...と思います(たぶん。というのも、サイトマップファイルには以下のような地味に面倒な仕様 / 制限があります。

  • 1 ファイルに含められる URL は最大 50,000 件
  • 50 MB 以下

もしこの制限に引っ掛かったときはサイトマップファイルを適切に分割し、サイトマップインデックスファイルを作ってあげなければなりません。これを簡単に調整できないと困ってしまうわけです。XSitemaps はこの辺りの制約に対して、

  • サイトマップファイルを指定の URL 件数単位で分割
  • インデントの有無の調整
  • GZIP 形式でのファイル圧縮 (= 仕様で認められている)

を自然な形でサポートしているので、そこが推しポイントというか便利なところです。

使い方

サイトマップファイル (Sitemap.xml) は以下のような感じで作ります。Sitemap.Serialize()Stream に書き込むこともできますし、byte[] で返すこともできます。

// URL から Sitemap を作る
var modifiedAt = DateTimeOffset.Now;
var urls = new[]
{
    new SitemapUrl("https://blog.xin9le.net", modifiedAt, ChangeFrequency.Daily, priority: 1.0),
    new SitemapUrl("https://blog.xin9le.net/entry/rx-intro"),
    new SitemapUrl("https://blog.xin9le.net/entry/async-method-intro", frequency: ChangeFrequency.Weekly),
};
var sitemaps = Sitemap.Create(urls, maxUrlCount: 2);  // 2 件ごとに分割

// ファイル出力
for (var i = 0; i < sitemaps.Length; i++)
{
    var desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
    var path = Path.Combine(desktop, $"Sitemap_{i}.xml");
    using (var stream = new FileStream(path, FileMode.CreateNew))
    {
        // 地味に気の利いたオプション
        var options = new SerializeOptions
        {
            EnableIndent = true,
            EnableGzipCompression = false,
        };
        sitemaps[i].Serialize(stream, options);
    }
}

// Sitemap_0.xml
/*
<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://blog.xin9le.net</loc>
    <lastmod>2020-01-12T00:07:12.2351485+09:00</lastmod>
    <changefreq>daily</changefreq>
    <priority>1</priority>
  </url>
  <url>
    <loc>https://blog.xin9le.net/entry/rx-intro</loc>
    <changefreq>never</changefreq>
    <priority>0.5</priority>
  </url>
</urlset>
*/

// Sitemap_1.xml
/*
<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://blog.xin9le.net/entry/async-method-intro</loc>
    <changefreq>weekly</changefreq>
    <priority>0.5</priority>
  </url>
</urlset>
*/

そして、サイトマップインデックスファイル (SitemapIndex.xml) は以下のような感じです。全然難しくない!

// 分割されたファイル情報から SitemapIndex を作る
var modifiedAt = DateTimeOffset.Now;
var info = new[]
{
    new SitemapInfo("https://example.com/Sitemap_0.xml", modifiedAt),
    new SitemapInfo("https://example.com/Sitemap_1.xml"),
};
var index = new SitemapIndex(info);

// ファイル出力
var desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
var path = Path.Combine(desktop, $"SitemapIndex.xml");
using (var stream = new FileStream(path, FileMode.CreateNew))
{
    var options = new SerializeOptions
    {
        EnableIndent = true,
        EnableGzipCompression = false,
    };
    index.Serialize(stream, options);
}

/*
<?xml version="1.0" encoding="utf-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap>
    <loc>https://example.com/Sitemap_0.xml</loc>
    <lastmod>2020-01-12T00:13:24.4802279+09:00</lastmod>
  </sitemap>
  <sitemap>
    <loc>https://example.com/Sitemap_1.xml</loc>
  </sitemap>
</sitemapindex>
*/

まとめ

SEO はエンジニアからすると比較的縁遠いものかもしれませんが、流入を気にする Web サービスの運営においては重要なことなので、もしサイトマップファイルを作ることがあれば使ってみてください。

2019 年の振り返りと 2020 年の抱負

明けましておめでとうございます!今年もいい年にしよう ZE!

ということで年末年始。大事なひと区切りなので、忘れないように 2019 年の振り返りと 2020 年の抱負を書き残しておこうと思います。

f:id:xin9le:20200101234830j:plain

プライベート

まず、2019 年の振り返りから。「2019 年を漢字 1 文字で表すと?」というのが年末恒例の質問としてあるので、それの真似をしてみると、僕のプライベートは「福」だったと思っています。

  • 3 年半ぶりに東京での単身赴任から福井に帰省
  • 家族との時間が増え、幸福感が爆上がり
  • ソシャゲを辞めて (謎の) 心的負担が無くなった

2019 年 2 月にシグマコンサルティング株式会社に転職したのですが、弊社のみなさんが僕のわがまま (= リモートワーク) を許可してくれたことに他なりません。本当に本当に感謝しています。ありがとう。

転職してみて

イマドキはもう結構メジャーな方法なのかもしれないのですが、転職は SNS で「仕事探してます!」と書き込むことで行いました。もう 1 年前かぁと懐かしくなりますが、そのときのご様子はこんな感じ。2 ch まとめに載ったのは良い思い出ですw

僕が東京に単身赴任を始めたのは 2015 年 9 月。娘が小学 1 年生になったのを契機として「そろそろいいかな」と修行目的で東京に単身赴任しました。現在は解体されてしまいましたが、C# 全開の会社 Grani に入りました。素敵なメンバーに恵まれたし、Web のソシャゲ (= 神獄のヴァルハラゲート)、モバイルネイティブのソシャゲ (= 黒騎士と白の魔王)、XR (= Grani VR Office Tour / Project Sonata) などなど色んなプロジェクトに関わらせてもらって、すごく密度の濃い時間を過ごすことができました。本当に東京に出て良かった。

それから 3 年半ほどの時が経ち、小学 1 年生だった娘が 5 年生になろうとしていました。比較的長く単身赴任をしていた割に僕と娘はメチャクチャ仲良し *1 なのですが、これから思春期を迎える娘とこれまで同様の関係でいられる時間はもう短いかもしれない *2 と思い、家族との時間を大切に過ごすために地元に帰る決心をしました。結果としてこれは本当に良い判断だったと思えていて、精神面で QoL の高い生活ができていると実感しています。仕事しつつ夏休みの娘の面倒を見ることだってできちゃう!

f:id:xin9le:20200101181204p:plain

東京の会社に勤め、東京ベースの給与でリモートワークしながら福井で生活する。福井のようなド田舎民としては理想的で先進的なライフスタイルだと思ってますし、実際同級生などと話をすると「そんな働き方もあるんだね」と驚かれます。すべて弊社に感謝です。

会社のみんなは四六時中、それこそ飲み会の場でもずっと IT や技術について語っているくらいの IT オタばっかりで心地よいです。雑談なのかミーティングなのかもはや分からないんですが、そういう空気感がとても好きです。良い選択をした。

ソーシャルゲーム

2017 年 4 月末のリリースから 2 年半、1 日も欠かさずずっとプレイし続けていた黒騎士と白の魔王を完全に辞めました。海外に行っていてもちゃんとログインし続け、時差やネットワーク環境をものともせず毎日ギルドバトルに参加するくらいガチでやってました。自社サービスをユーザー目線で理解するために始めたのですが、正直どっぷりと浸かり過ぎました。Grani を離れてからすぐ辞めてもよかったんですが、数十万課金したという勿体なさもあって辞め時を失ったというのもあります。ちゃんと長くプレイしたことで運営目線 / ユーザー目線の両方をしっかり得ることができ、経験として非常によかったです。今振り返ってみると、課金して懐を痛めた分の元は十分に取れているなと素直に思えています。

そして辞めた今はとても清々しい。ハマり症が発動すると人生ブッ壊れる可能性もあって本当にヤバいのが身に染みたので、今後ソシャゲはやらないでしょうし、長く続けることもないでしょう*3。あと色々な理由によりネカマしてたので、それも正直相当しんどかった...(とは

タピオカドリンク

2019 年の最初のうちだけですが、タピオカにハマりまくりました。Uber Eats を使って THE ALLEY のタピオカ (大) をひとりで 3 杯とか 4 杯とか注文して、家から一歩も出ないでタピオカだけで 1 日生活するとかやってました。正直狂ってました。そのせいか若干太った気がします。

f:id:xin9le:20200101180818p:plain

ついムシャクシャしてやった。今は反省している。

仕事 / プログラミング

プライベートだけではアレなので (?) 仕事面でも 1 文字だけで表してみると、「離」でしょうか。

  • 福井でリモートワークを開始
  • 守破離の「離」に近づけた気がする

リモートワーク

すでに何度かキーワードとして出していますが、僕にとってリモートワークは大きな転機となりました。弊社は会社に出社するか / 自宅作業をするかは裁量の範囲内ですが、それは社員に働き方の裁量を与えることが仕事のパフォーマンスを最大化することに繋がると社長および役員が考えてくださっているからです。自宅で仕事をする方が効率が良いときもあれば、オフィスに行った方が効率が良いときもあります。定時出社なんてものに縛られないのは極めて効率が良いでしょう。なので東京にいる社員のみんなも適宜リモートワークをしています。僕だけが福井を拠点にしているのですが、それも「20 km とかが 500 km くらいになっただけで一緒でしょ」と言ってくれたことが本当に嬉しかったし、大きかったです。

ただ、それでもやっぱり「何かあったらオフィスに行けばいい」と簡単にできない距離ではあります。概ね毎月 1 週間くらい東京オフィスに顔を出すようにしていますが、それでもどうしても Slack 上だけでは把握しきれない空気感もあれば、コミュニケーション量も大きく違うので知らないことも出てきてしまいます。これを最大限カバーするために、僕は担当外のプロジェクトの Slack チャンネルにもほとんど参加していて、誰がどんな仕事をしているのかを積極的に情報収集するようにしています。リモートワークをしている上で常に心がけているのは、「すぐに返事をすること」と「いっぱい書き込むこと」です。幽霊社員だと他メンバーに感じさせないこと、ちゃんと見ているよというアピール、相手を待たせない姿勢が大切です。

それと東京オフィスにカメラを置いてくれたのも本当に良かった点でした。スマホからオフィスの様子を見たり、会話を聞くことも、喋りかけることもできます。これによって東京オフィスでの会話を環境音として聞きながら仕事ができるようになったので、寂しさや孤独感が一切なくなりました。

守破離

僕のプログラミングの師匠は Microsoft MVP (C#) の小島さん (@Fujiwo) で、新卒で入社した会社のプログラミング研修の担当でした。半年にも及ぶ (超厳しい) 研修を受けて育ったのは良い思い出で、それが今の礎になっています。そのとき習ったもののひとつが守破離です。

  • 守 : 師の訓えや支援のもとに作業を遂行できる (= 0.5 ~ 1 人前)
  • 破 : 師の訓えを分析し、改善 / 改良できる (= 1.5 人前)
  • 離 : 師を離れ、新たな知識 / 技術を開発できる (= 創造者)

僕のプログラミングスタイルや考え方は小島さんから教わったことが今でも色濃く出ていて、他にもまだまだありますが、例えば以下もそのひとつです。

そして昨年、メチャクチャ速い .NET の Enum ユーティリティ FastEnum を作りました。ひとつの成果を残すことができたと思っていますし、師匠にも十分褒めて貰えるものだと自負しています。当然すべてを超えることはできませんが、ひとつ自分の中で守破離の「離」に達した気がしました。

プロダクト開発

受託のお仕事ということもあり大っぴらに「コレ」と言い切るのは憚られるのでぼかしますが、アンテナを張っている IT 業界の人なら絶対知っているであろう新進気鋭の某プラットフォームのバックエンドを作ったり、美容に気を遣う女性ならほぼ知っているであろう某ブランドの EC の運営をしていたりしました。認知度の高い大物案件を少人数でやるというのは達成感ややりがいがあって善きですね。

コミュニティ活動 / オープンソース開発

2019 年は結構頑張ったんじゃないかなーと思える年でした。仕事がバカみたいに忙しかったのによくこれだけやれたなぁと...w

オープンソース開発

仕事で自分が使いたいと思えるものを中心に手を付けて実装 / 検証していきました。ちょっとオーバーエンジニアリングかなと思うところもあるのですが、自己満足と学習込みで結果として良かったかな。

登壇

登壇やお手伝いで 7 つだから 2 ヵ月に 1 度以上のペース。これは正直仕事に差し支えてるのでは...(白目

de:code は初登壇なのに大トリ (= 2 日目最終コマ) で緊張しましたが、ありがたいことに満点に近いハイスコアをいただけました。本当に感謝!北陸を中心に数年やってきたエンターテイメント重視のコンテンツですが、他のセッションとは一線を画しているのは間違いないかと思います。また機会に恵まれれば、大きなイベントの余興でもなんでもやってみたいと考えています。

Visual Studio Users Community Japan では C# / .NET における高速化 Tips 集 をやりました。スライドが結構評判が良かったようで PV が 12000 を超えていました!お役に立てているのであれば嬉しい限りです。

2020 年の目標

プライベート

基本的に現状維持できるように頑張ります。思春期を迎える娘とどう接するかは僕の大きな課題ですが、基本的には寛大に、要所要所を見極めてビシッと手短に締めるという方針は変えずにやっていければと思っています。あと老後に向けた預貯金と資産運用を頑張る。年金問題 (?) に耐えられるように将来設計しなければ...(強い意志

仕事

仕事の面では「改」をキーワードとして、より安全に、より柔軟性高く顧客の要望に応えられるよう地道な改善 / 改良を進めて行こうと思っています。猪突猛進ではなく、将来を見据えて腰を据えて戦って行けるような土台作りをして行ければと。

僕は拠点がひとり離れたリモートワーカーなので、チーム的には顧客と対話したりする PM 的立ち位置ではありません。お客様は東京だけにいるわけではないですが、やはり東京が多いのでこうなっています。その分僕は当然コードをガリガリ書くことでチームに貢献しなければなりませんし、今年も 誰よりも Git に Commit しまくることで結果にコミットできればと思っています。

*1:嫁の子育ての賜物

*2:一般的に父親は敬遠されると言われているので

*3:ソシャゲ自体がダメとは言ってない