2022年10月7日金曜日

Celeryでプリフェッチ(RECEIVED)されないための設定

Celeryでプリフェッチ(RECEIVED)されないための設定

概要

celery にはプリフェッチという機能がありワーカーが実行中であっても次のジョブを事前に取得して RECEIVED として管理する機能があります
RECEIVED になったジョブはワーカーが保持するすべてのジョブが終了するまで実行されないので長い処理を実行中のワーカーにプリフェッチされると RECEIVED で永遠に実行されないジョブが発生してしまいます
また RECEIVED の状態で1時間経過すると再度ジョブをエンキューするという謎の仕様がありこのせいで同一ジョブが複数回実行されるというバグもあるようです (参考)

今回のこのプリフェッチを無効にする方法があるとのことなので試してみました

環境

  • macOS 11.7
  • Python 3.10.2
  • celery 5.2.7
  • flower 1.2.0

設定ファイル

worker_prefetch_multipliertask_acks_late を設定します

  • vim celeryconfig.py
result_backend = 'redis://localhost'
broker_url = 'redis://localhost'
worker_prefetch_multiplier = 1
task_acks_late = True

タスク

プリフェッチされているかを確認するためにあえて wait を入れています

  • vim tasks.py
from celery import Celery

app = Celery('tasks')
app.config_from_object('celeryconfig')

@app.task
def add(x, y):
    import time
    time.sleep(3600)
    return x + y

@app.task
def multi(x, y):
    return x * y

メイン

  • app.py
from celery import chain
from tasks import add, multi

tasks = chain(add.s(1, 2),
              multi.s(10),
              multi.s(2)).apply_async()

動作確認

  • pipenv run celery -A tasks worker --loglevel=info

ワーカーを起動しいくつかのジョブを登録します

  • for i in `seq 0 5`; do pipenv run python app.py; done

すると4つのジョブはSTARTEDになりますが残り2つはプリフェッチされず RECEIVED にもならないことが確認できます

ちょっと流れを解説

redis 内で管理されているキーの説明を少しします

STARTED になったタスクは unacked と unacked_index というキーで管理されています
それぞれ Hash 型と ZSet 型で管理されています

まだどのワーカーにも属していないタスクは celery というキー (キュー) 内で管理されています
celery は List 側になっています

最後に

注意点としては worker_prefetch_multiplier が 3,4 系では使えない点です (4.2 から使えるようになった?)

詳細は不明ですが素直に5系にアップグレードしてから使ったほうが良いかなと思います

プリフェッチすらされていないジョブのタイムアウトなどあるのか気になりました

参考サイト

2022年10月6日木曜日

Quasarでダークモードを設定する方法

Quasarでダークモードを設定する方法

概要

$q.dark を設定するだけでダークモードにできます
今回はボタンで切り替えるサンプルを紹介します

環境

  • macOS 11.7
  • node 18.9.0
  • quasar 2.8.2

サンプルコード

  • vim src/pages/IndexPage.vue
<template>
  <div>$q.dark = {{ mode }}</div>
  <q-btn 
   color="white"
   text-color="black"
   label="Toggle"
   @click="toggleDark()"
   />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useQuasar } from 'quasar'

const $q = useQuasar();
const mode = ref($q.dark.isActive);

$q.dark.set(false);

const toggleDark = () => {
  $q.dark.toggle();
  mode.value = $q.dark.isActive;
}
</script>

ちょっと解説

useQuasar で q オブジェクトを取得します
あとは dark プロパティがあるのでそれに true or false or auto を設定します

auto の場合システム側の環境に合わせて自動的に切り替えてくれます

動作確認

参考サイト

2022年10月5日水曜日

Celery の状況を UI で管理できる flower を試してみた

Celery の状況を UI で管理できる flower を試してみた

概要

celery には flower という管理 UI の機能があります
今回はインストールから実際に起動して動作確認するところまで行ってみました

環境

  • macOS 10.15.5
  • Python 3.8.3
    • celery 4.4.4
    • flower 0.9.4 (1.2.0)

flower のインストール

  • pipenv install flower

flower の起動

  • pipenv run flower -A tasks --broker=redis://localhost:6379 --port=5555

--broker で celery が使っているブローカを指定します
--port でポートを指定できます
デフォルトは 5555 になっています

P.S 20221005

flower1.2.0では以下のように起動コマンドが変わっているのでご注意ください

  • pipenv run celery -A tasks flower --broker=redis://localhost:6379 --port=5555

動作確認

今回使うタスクは以下の通りです

from celery import Celery

app = Celery('tasks', broker='redis://localhost:6379')

@app.task
def add(x, y):
    print(x + y)

起動しましょう

  • pipenv run celery -A tasks worker -l info

あとは何回かタスクを消化させてみます

from tasks import add

add.delay(100, 1)
  • pipenv run python test.py

これで localhost:5555 にアクセスしてみましょう
起動しているワーカの一覧が表示されると思います

ワーカをクリックすると詳細情報が確認できます
プールサイズの変更なども画面からできるようです
またタスクを実行するキュー (Consumer) も画面から追加できるようです

最後に

celery の管理 UI である flower を試してみました
ブローカを指定して起動するだけなので簡単です
ある程度のワーカの設定変更もできるのでプロセスを再起動することなく操作できるのは嬉しい点かなと思います

認証を追加することもできるようなので特定のユーザにのみ閲覧させることも可能です

参考サイト

Quasarでフィールドに入力した値から候補を補完する方法

Quasarでフィールドに入力した値から候補を補完する方法

概要

select + filter を使うことで入力した値から候補を絞り込むことができます

環境

  • macOS 11.7
  • node 18.9.0
  • quasar 2.8.2

サンプルコード

  • vim src/pages/IndexPage.vue
<template>
  <div class="q-pa-md">
    <div class="q-gutter-md row">
      <q-select
        filled
        v-model="model"
        use-input
        fill-input
        hide-selected
        clearable
        :options="options"
        @filter="filterFn"
        hint="Basic filtering"
        style="width: 250px;"
      >
        <template v-slot:no-option>
          <q-item>
            <q-item-section class="text-grey">
              No results
            </q-item-section>
          </q-item>
        </template>
      </q-select>
    </div>
  </div>
  <div class="q-pa-md">
    <q-btn
     label="Search"
     @click="search()"
    >
    </q-btn>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const stringOptions = [
  'Google', 'Facebook', 'Twitter', 'Apple', 'Oracle'
]

const options = ref(null)
const model = ref(null)

const filterFn = (val, update, abort) => {
  update(() => {
    const needle = val.toLowerCase()
    options.value = stringOptions.filter(v => v.toLowerCase().indexOf(needle) > -1)
  })
}

const search = () => {
  console.log(model.value);
}
</script>

ちょっと解説

q-select の options と filterFn が重要です
options は q-select の候補となる一覧を設定するためのリアクティブモデルです

filterFn は入力のたびに update が呼び出されます
update では入力された値を小文字に変換し入力された値とオプションの文字列を比較します
入力された値がオプション内に含まれている場合はそれを残し再度 options に設定することで入力の候補を絞り込んでいます

q-select で選択された値は v-model で設定した model リアクティブオブジェクトにに入っているのでその値を Search では表示しています

参考サイト

2022年10月4日火曜日

Quasarでカルーセルを表示するサンプルコード

Quasarでカルーセルを表示するサンプルコード

概要

q-carousel の使い方をサンプルコード付きで紹介します
今回は q-btn-toggle を組み合わせて使います

環境

  • macOS 11.7
  • node 18.9.0
  • quasar 2.8.2

サンプルコード

  • vim src/pages/IndexPage.vue
<template>
  <div class="q-pa-md">
    <q-carousel
      v-model="slide"
      transition-prev="slide-right"
      transition-next="slide-left"
      animated
      swipeable
      control-color="primary"
      class="rounded-borders"
    >
      <q-carousel-slide name="style" class="column no-wrap flex-center">
        <q-icon name="style" color="primary" size="56px" />
        <div class="q-mt-md text-center">
          {{ one }}
        </div>
      </q-carousel-slide>
      <q-carousel-slide name="tv" class="column no-wrap flex-center">
        <q-icon name="live_tv" color="primary" size="56px" />
        <div class="q-mt-md text-center">
          {{ two }}
        </div>
      </q-carousel-slide>
      <q-carousel-slide name="layers" class="column no-wrap flex-center">
        <q-icon name="layers" color="primary" size="56px" />
        <div class="q-mt-md text-center">
          {{ three }}
        </div>
      </q-carousel-slide>
    </q-carousel>

    <div class="row justify-center">
      <q-btn-toggle
        glossy
        v-model="slide"
        :options="[
          { label: '1.', slot: 'one', value: 'style' },
          { label: '2.', slot: 'two', value: 'tv' },
          { label: '3.', slot: 'three', value: 'layers' },
        ]"
        @update:model-value="updateBtn"
      >
        <template v-slot:one>
          <div class="row items-center no-wrap">
            <div class="text-center">
              {{ one }}
            </div>
            <q-icon right name="style" />
          </div>
        </template>

        <template v-slot:two>
          <div class="row items-center no-wrap">
            <div class="text-center">
              {{ two }}
            </div>
            <q-icon right name="live_tv" />
          </div>
        </template>

        <template v-slot:three>
          <div class="row items-center no-wrap">
            <div class="text-center">
              {{ three }}
            </div>
            <q-icon right name="layers" />
          </div>
        </template>
      </q-btn-toggle>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';

const slide = ref('style');
const one = 'Hello One!'
const two = 'Hello Two!'
const three = 'Hello Three!'

const updateBtn = async(value) => {
  if (value == 'style') {
    console.log(value);
  }
}
</script>

ちょっと解説

q-carousel と q-btn-toggle に同じ v-model を設定することでボタンの選択に合わせてカルーセルを追従させることができます

swipeable を設定することでドラッグで移動することができるようになります

q-btn-toggle 側は今回は slot を使ってボタンをそれぞれテンプレートとして定義しています
この方がボタンに表示する内容を細かく定義できるのでおすすめです

q-btn-toggle のボタンが変わった場合のイベントは @update:model-value を使います
こうすることで options で定義した value の値を関数内で拾うことができるので押されたボタンの種類に応じて処理をわかることができます

参考サイト

2022年10月3日月曜日

Quasarでダイアログを表示するサンプルコード

Quasarでダイアログを表示するサンプルコード

概要

q-dialog の使い方をサンプルコード付きで紹介します

環境

  • macOS 11.7
  • node 18.9.0
  • quasar 2.8.2

サンプルコード

  • vim src/pages/IndexPage.vue
<template>
  <div class="q-pa-md q-gutter-sm">
    <q-btn color="white" text-color="black" label="Show" @click="showDialog" />
  </div>
  <q-dialog v-model="dialog" persistent>
    <q-card>
      <q-card-section class="row items-center">
        <q-avatar icon="signal_wifi_off" color="primary" text-color="white" />
        <span class="q-ml-sm">You are currently not connected to any network.</span>
      </q-card-section>

      <q-card-actions align="right">
        <q-btn flat label="Cancel" color="primary" v-close-popup />
        <q-btn flat label="Turn on Wifi" color="primary" v-close-popup />
      </q-card-actions>
    </q-card>
  </q-dialog>
</template>

<script lang="ts" setup>
import { ref } from 'vue';

const dialog = ref(false)

const showDialog = async() => {
  dialog.value = !dialog.value
}
</script>

ちょっと解説

q-dialog の表示非表示は v-model で指定したリアクティブオブジェクトの真偽値で切り替えます

ボタンをクリックしたときに真偽値を切り替えることで動的に切り替えることができます
ボタンクリック時の関数内でリアクティブオブジェクトの値を変更する場合は .value プロパティを使って変更します (直接リアクティブオブジェクトの置き換えは const なのでできない)

q-dialog で persistent を使うとダイアログ内のキャンセルボタンを押すまではダイアログが非表示になりません
確実な入力を求める場合には persistent を使います

また今回のようにボタンクリック時のアクションをわざわざ関数に切り出さなくても @click で直接リアクティブオブジェクトを操作することもできます
しかしその方法だと例えばダイアログ内に表示する内容を動的に変更したい場合に対応できません
なので今回はそういったケースにも対応できるようにわざわざ関数に切り出して表示非表示を切り替えるようにしています

参考サイト

2022年10月1日土曜日

v-modelは変数とコンポーネントの双方向データバインドをしてくれる

v-modelは変数とコンポーネントの双方向データバインドをしてくれる

概要

いろいろなところで丁寧な説明がされていますが一応自分が忘れないようにメモ

環境

  • macOS 11.7
  • node 18.9.0
  • quasar 2.8.2

サンプルコード

  • vim src/pages/IndexPage.vue
<template>
  <div>
    <input type="text" v-model="value" />
    <p>Value: {{ value }}</p>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

const value = ref('')
</script>

ちょっと解説

input の v-model="value" は script ないで定義した value 変数を指している
p タグ内の value も当然変数の value を指している

input のフォームに入力した値はフォームにも反映されるし同時に value 変数にも反映される

これが双方向データバインドの意味になる

ちなみに v-bind という同じような機能もあるがこれは一方向データバインドになる

参考サイト