#personalizedset
Explore tagged Tumblr posts
Text
Discover trendy pet accessories at CurliTail! Shop stylish, comfortable, and high-quality collars, leashes, and more for your furry friend. https://curlitail.com/
0 notes
Text
Shop the Vector Personalized Pet Toy Basket at CurliTail. Stylish, durable, and custom-made for your pet. Perfect for organizing toys. Made in the USA. https://curlitail.com/.../vector-personalized-pet-toy-basket

#personalizedset#PersonalizedPetBasket#PetToyBasket#PetOrganization#PetStorage#PetToyStorage#PetBasket#PetAccessories#CustomPetBasket
0 notes
Photo

Organic Pillow & Bolster Set Organic pillow & bolster set at little west street. Snugly soft organic pillow and bolsters for baby’s extra comfort. Grab some unique and attractive Kids accessories online that makes your kid shine
0 notes
Link
For just $148.00 --------------------------------------------- -------- SAVE $20 ------------ --------------------------------------------- CLASSIC MOTORCYCLES Original drawings prints Digitally printed on 200 grs acid free paper, professional quality. Image size 11 x 14 inches (for standard frames) The watermark not appear in prints. NOTE: Colors may vary from screen to screen. This price is for Registered shipment! Unframed Prints will be shipped in a cellophane sleeve. I send my prints off to their new home within 1-3 days of payment. I ship my international orders by air after I'm notified of your payment, and taking between 10 to 20 days to arrive. The purchase of any print does not transfer reproduction rights. ©Pablo Franchi -------------------------------------------------------------------------------------------------- Motorcycles BY YEAR: https://www.etsy.com/shop/drawspots?ref=hdr_shop_menu§ion_id=19206680 Motorcycles BLACK & WHITE: https://www.etsy.com/shop/drawspots?ref=hdr_shop_menu§ion_id=19206694 If you like CARS, take a look here: https://www.etsy.com/shop/drawspots/items?section_id=11083041 MUSICIANS, JAZZ portraits & quotes: https://www.etsy.com/shop/drawspots?section_id=11220720 ----------------------------------------------------------------------------------------------------
#CustomizableSet#HomeDecor#SetOfPrints#SetOf4#NortonBikes#ArtPrint#PersonalizedSet#BmwMotorcycles#OriginalDrawing#FathersDayPrint
0 notes
Photo

Gift Set of keychains for an upcoming wedding for the bride and groom🌹 #beautyandthebeast #customkeychains #couplesset #hisandhers #keychainset #pennykeychains #penny #custompenny #weddinggift #brideandgroom #hisbeautyherbeast #luckyinlove #initials #weddingday #daytocelebratelove #handstampedkeychain #personalizedset #personalizedkeychain #giftswithmeaning #makingthingspersonal #sibellejewelry
#hisbeautyherbeast#pennykeychains#keychainset#makingthingspersonal#personalizedkeychain#initials#handstampedkeychain#beautyandthebeast#hisandhers#customkeychains#weddinggift#giftswithmeaning#personalizedset#weddingday#couplesset#daytocelebratelove#sibellejewelry#luckyinlove#penny#brideandgroom#custompenny
0 notes
Text
オブジェクト指向プログラミングにおける Redis Sorted Set の抽象化
(本稿は 技術書展4 にて頒布される WANTEDLY TECH BOOK 4 に掲載される予定の文章です)
はじめに
Redis の Sorted Set を使った一覧画面の実装パターン
Web サービスにおいて Redis の Sorted Set は高速なランキングの実現のためにしばしば用いられる。ランキング・データを Redis に載せることで、高速なデータの取得・ページング・件数計算などが可能になる。ランキングのように順序付けされたコンテンツは Web サービスの一覧画面と呼ばれるページにおいて存在するが、このような画面においてはしばしばリテンションが重要である。レスポンス速度はこのリテンションに対して影響を与えるため、高速な実装が求められる。
一方、時系列で考えた場合、一覧画面の要件はサービスの扱うコンテンツ数が増加するに従って複雑化する。例えば、ユーザーが適切なコンテンツを発見できるようにする必要が出てきて、そのために様々な条件による絞り込みや検索、あるいはパーソナライズされた推薦と言った機能が必要になってくる。
これらの機能をどのように実現したら良いだろうか。通常、絞り込みに使われるようなコンテンツのデータはリレーショナルデータベースに保存されている。一方で、検索の実現には Elasticsearch などの全文検索エンジンが採用されることが多い。また、昨今では機械学習が活用されることも多いが、そのような処理を行うためのライブラリは Python が充実している。このように、ユーザーに価値を提供できる一覧画面に求められる機能は多く、またそれを実装するために最適な技術は必ずしも一つには定まらない。
マイクロサービス・アーキテクチャを採用している場合、各機能を実現するのに最適な技術に基づいたマイクロサービスを実装し、それらを組み合わせることによって一覧画面を実現することが考えられる。しかしこのとき、各マイクロサービスをどのような方法で統合するのかということが課題になる。
既に述べたように Redis の Sorted Set はランキングに利用されることが多いが、和集合・積集合など組み込みの集合演算の命令を使うことで絞り込みや検索、あるいはパーソナライズと言ったより高度な機能を実現することができる。そこで、異なるシステムから集めた情報をそれぞれ別個の Redis の Sorted Set にキャッシュした上で、それを集約計算して一覧画面に必要な情報を算出するような構成を考えることができる。
この構成では、任意のマイクロサービスやデータベースからの情報をスコア付きの集合とみなした上で統合することができる。また、レスポンスに必要な全てのデータは一度 Redis に乗るため、計算の内容も様々である各システム群がサービスとして求められているレスポンス速度を直接満たさなくても全体として高速に動作すると言った利点もある。
事例
Wantedly Visit では数十万件の募集が存在するが、その一覧画面( https://www.wantedly.com/projects )は上記のような要件��持つ一例である。この画面は実際にこの構成とこれから紹介する RedBlocks を使って実現されている。
実現する上での課題
課題1: オブジェクト指向プログラミングと Redis のコマンド体系のインピーダンス・ミスマッチ
このように Redis の Sorted Set を用いることで一覧画面に必要な機能を実現することができるわけだが、その実装を Redis のコマンドをそのまま呼び出す形で行うと、Ruby などのオブジェクト指向プログラミング言語に於いては高い可読性・変更容易性を実現することができない。
具体的には、Redis のコマンド体系が命令的なものであるため、それをそのまま利用すると手続き的なプログラムになってしまう。この問題は、 単純なキャッシュ目的で Redis を利用しているときには大きく問題にならないが、今回述べたように複雑なシステムを構築する際には問題となる。具体的には、何かアプリケーションの変更を行うときに変更箇所を見つけ出すことが難しかったり、複数の箇所を変更する必要が生じる(変更に対してモジュラでない、関心が分離されていない、と言った状態になる)。
# 非常に単純な集合演算を書くだけでも多くの命令が必要にになる c = Redis.new c.zadd("src1", [[32.0, "a"], [64.0, "b"]]) c.expire("src1", 60) c.zadd("src2", [[44.0, "a"], [21.0, "c"]]) c.expire("src2", 60) c.zinterstore("dest", ["src1", "src2"]) c.expire("dest", 10) c.zrevrange("dest", 0, 5) #=> ["a"] c.zcard("zcard") #=> 1
通常、リレーショナル・データベースをオブジェクト指向の言語から利用する場合、集合指向との概念的な差異(あまり良いメタファーとは言えないが「インピーダンス・ミスマッチ」と呼ぶことがあるので本稿でも用いる)が問題になるため、O/R Mapper による抽象化を行った上で利用することが多い。同様の理由で、Redis の Sorted Set を Ruby から利用する際も OOP に即した形で抽象化を行うことでこの問題を解決することが必要になる。
解消すべき差異の例として、クラスベースの OOP においては、クラスはそれ自体で直接アプリケーションに必要な仕事を果たすわけではなく、インスタンス化を通じてそれを行う。Redis の Sorted Set にクラスとインスタンスのような概念は無いため、key の階層化を通じてこれを実装する必要がある。
課題2: 実装に必要な多くの詳細
実際にサービスで利用するためには概念的な差異以外にも多くの隠蔽できる実装詳細がある。例えば、次のようなことは本来、都度アプリケーションを開発するプログラマが考える必要のないことである。
キャッシュの存在の有無をチェックしてなければデータを取得し Redis に保存する
キャッシュが無い時間が存在しないように差分更新にする
直列依存性のないコマンドのパイプライン実行
Redis の key に使う名前
このような実装詳細を隠蔽することで、アプリケーションを開発する際、
1) サービスの仕様として各マイクロサービスをど��組み合わせるか(AND なのか OR なのか、スコアをどう組み合わせるか)
2) それぞれの計算段階におけるデータをどの程度キャッシュするか
と言った統合に関する仕様(これはサービスの価値に直結する)にプログラマは集中できるようになる。
RedBlocks
具体的に上記のような問題を解決するために、、RedBlocks という Ruby ライブラリ(gem)を用意した。
以降では、RedBlocks を通じてどのように複数のシステムを組み合わせるかを、実際の一覧画面の実装を通じて紹介する。その後、いくつかの実装詳細について言及し、最後にまとめとする。
Case Study: RedBlocks を用いた一覧画面の実装
この章では、課題1として述べたオブジェクト指向プログラミングと Redis のコマンド体系のインピーダンス・ミスマッチがどのように解決できているのかを実例を通して説明する。そのために、次のような一覧画面を想定する。
この画面には、主に次の4つの機能が備わっている。また、共通する機能として件数表示とページングができる。
ランキング(初期状態)
絞り込み
全文検索
パーソナライズ(ユーザー登録して利用している場合)
これらの機能を順次実装していき、最後にそれをユーザーの操作に応じて行う service クラスを実装する。
1. ランキング
ほぼ全てのサービスにおいて、一覧画面ではコンテンツ(以下、要素)をどのような優先度で表示するのかということを考える必要がある。これはランキングに相当する。全てのユーザーに同じ順番で見せる場合、ランキングを一つだけ用意すれば良い。ランキングは、それぞれの要素に対してスコアが付いた集合、と捉えられるため、Redis の Sorted Set を利用して実装することができる。
ここでは、一覧画面が扱う要素として募集(Project)を想定した上で、カラムとして monthly_page_view を持った Project というActiveRecord モデルがあるするとする。このカラムの値を利用して PV ベースのランキングを作成したい。このための RedBlocks を用いたコードは次のようになる。
class PageViewSet < RedBlocks::UnitSet def cache_time RedBlocks::CachePolicy.hourly end def get Project.pluck(:id, :monthly_page_view) end end
このコードは、まず RedBlocks::UnitSet を継承することで集合(Sorted Set)を表すクラス PageViewSet を定義している。この集合は要素として、月間ページビューをスコアに持った募集含んでいる。
このクラスを利用することで、例えば次のようにランキングの先頭5件を取得できる。
pv_set = PageViewSet.new pv_set.ids #=> [1942, 3921, 354, 1120, 4931] pv_set.size #=> 5121
ids メソッドが呼ばれると、初回は get メソッドの呼び出しを通じて Project クラスからクエリが発行される。その結果は Sorted Set としてキャッシュされた上で、ids メソッドの返り値となる。二回目以降の ids メソッドの呼び出しではキャッシュが利用されるため非常に高速に返る。同様に、全体の件数も size メソッドを通じてキャッシュされた集合から定数時間で取得することができる。 また、ids メソッドに paginator オブジェクトを渡すことで、定数時間でのページネーションも可能になる。実際の Web サービスの実装では、リクエストパラメータとして渡ってきたページ番号をそのまま渡すことが多いと思われる。
このキャッシュをどの程度の期間 Redis 上に保持するかは cache_time メソッドによって決めることが出来る。このメソッドを定義しない場合、キャッシュする時間はゼロとなり、データは Redis 上には最低限の時間しか生存しない(キャッシュしない場合でも Redis 上にデータが生存する必要がある理由は後述する)。
cache_time は鮮度やコストに応じて必要であれば定義すれば良いので、これ以降のサンプルコードでは省略する。
2. 絞り込み
地域の募集の集合 - RedBlocks::Set の利用
サービスのコンテンツが増加してきたことで、募集を地域によって絞り込む機能を提供したくなったとしよう。地域は、「北海道」「東北」「関東」「北陸」「甲信越」「東海」「近畿」「中国」「四国」「九州」「沖縄」の11地域に分類されるとする。このために、まずは地域ごとの募集の集合を表すクラスを定義する。
class RegionSet < RedBlocks::Set def initialize(region) raise ArgumentError unless Project::REGIONS.include?(region) @region = region end def key_suffix @region end def get Project.where(region_cd: @region).pluck(:id) end end
このクラスは次のように具体的な region を引数に与えてインスタンス化することで、先ほどのランキング集合の場合と同様に利用することができる。
set = RegionSet.new('hokkaido') set.ids #=> [1, ... ] # 北海道の募集のリスト
PageViewSet と RegionSet の違いは、PageViewSet は RedBlocks::UnitSet を継承していたのに対して RegionSet は RedBlocks::Set を継承している点と、RegionSet は key_suffix メソッドを実装している点にある。
RegionSet は各地域に対してそれぞれ異なる集合があり得るため、インスタンスとして生成する際に具体的な地域の指定が必要である。更に、Redis に保存する際の保存先も、例えば「北海道地域の募集の集合」と「関東地域の募集の集合」では異なるため、その保存先を指定するための Redis の key の1部分として key_suffix メソッドを実装する必要がある。key_suffix メソッドはインスタンスレベルで異なる集合を区別できるような文字列を返す��うに実装する必要がある。
一方、最初に紹介した PageViewSet は実質的にクラスにただ一つの集合しか作り得ないシングルトン集合であったため、それに特殊化した RedBlocks::UnitSet を利用した。このクラスはシングルトンを前提としているため、key_suffix メソッドを実装する必要はない。
また、get メソッドの返り値の型に着目してみると、PageViewSet#get は募集の id とスコアのペアのリストを返しているが、RegionSet#get はただ募集の id のリストを返していることが分かる。RedBlocks の get メソッドでは、id のリストを返した場合に内部的に全てスコア0の Sorted Set として扱う。Redis の集合演算ではスコアはデフォルトで同一要素同士で加算されるため、スコアを暗黙的に零元に設定しておくことでスコアに影響を与えない集合として扱うことができる。プログラマは、スコアを持たない(つまり数学的な意味での)集合を利用したいときにはただ要素のリストを返すだけで良い。今回の場合、地域による絞り込み動作は順序には関与しない仕様にしたいため、RegionSet#get はただ要素のリストを返している。
複数の地域に含まれる募集の集合 - RedBlocks::UnionSet の利用
ここまでで特定の地域による絞り込みができるようになったが、「『北海道』または『関東』の募集を探したい」と言ったユーザーのニーズにも応えられるようにしたい(ユーザー・インターフェースとしては、チェックボックスのように複数選択式のものになる)。
これは、集合で言えば北海道の募集と関東の募集の和集合を作ることに相当する。従って、次のような定式化ができる:
[北海道の募集] ∪ [関東の募集]
RedBlocks::UnionSet を使うことで、この和集合演算を実現できる。
region1_set = RegionSet.new('hokkaido') region2_set = RegionSet.new('kanto') regions_set = RedBlocks::UnionSet.new([region1_set, region2_set]) regions_set.ids #=> [921, 324, 21, 39, 101]
公開されている募集のみに絞る - RedBlocks::IntersectionSet の利用
実際のサービスでは、コンテンツには下書き・非公開などの状態が存在し、必ずしもデータベースに登録されている全てのコンテンツを一覧画面に出せるわけではない。このことを制御するため、次のように公開募集の集合で積集合演算を行った結果を常に返すようにしたい:
[公開されている募集] ∩ ([北海道の募集] ∪ [関東の募集])
RedBlocks::IntersectionSet を使うことで、この積集合演算は実現できる。
visible_set = VisibleSet.new result_set = RedBlocks::IntersectionSet.new([visible_set, regions_set])
さて、公開・非公開などの状態の変更は特にリアルタイムで一覧画面の結果に反映したいものである。そのため、募集の状態の変更に応じて RedBlocks::Set#update! を呼び出して即座にキャッシュを更新するようにする。
class Project < ActiveRecord::Base # ... after_save -> { VisibleSet.new.update! }, if: -> { visibility_changed? && visible? } end
絞り込み結果を PV でソートする
地域による絞り込みを行った結果も、絞り込む前の最初に紹介した PV ランキングに基づいて表示されるようにしたい。これは、最初に作成した PageViewSet を積集合演算に組み込むことで実現できる。
[PageView をスコアに持つ募集] ∩ [公開されている募集] ∩ ([北海道の募集] ∪ [関東の募集])
PageView ではなく最新順で表示すると言ったソート条件の切り替え機能を提供するのであれば、この積集合演算に使うスコア付き集合を変更すれば良い。
[公開日時をスコアに持つ募集] ∩ [公開されている募集] ∩ ([北海道の募集] ∪ [関東の募集])
3. 全文検索
サービスに十分なコンテンツ量と十分多様なユーザーが集まったため、任意のキーワードによる検索機能を入れてより目的の募集に出会いやすくしたい。これまでの計算はリレーショナル・データベースのデータを元にしていたが、今回の機能追加ではよりマッチ度が高い要素から順番にユーザーに表示したいため、全文検索エンジン Elasticsearch へ問い合わせた結果のスコアを採用することにした。
このために特定のキーワードを引数に受け取って Elasticsearch に問い合わせる次のようなモジュールを作成した。Elasticsearch には募集のテキスト情報を同期している。
ProjectSearchService.new("Ruby", format: :id_with_score).perform #=> [[32, 12.1], [811, 11.0], ...]
このモジュールを元に、RegionSet の場合と同様に集合を定義する。RegionSet との違いは、全部検索による該当キーワードへのマッチ度が入ったスコア付きの集合となっている点にある。
class KeywordSet < RedBlocks::Set def initialize(keyword) @keyword = keyword end def key_suffix @keyword end def get ProjectSearchService.new(@keyword, format: :id_with_score).perform end end
この KeywordSet を用いて、例えば次のような集合を構築できればキーワード検索機能を付加することができる。キーワードによるスコアが既に付いているため、ここではPVランキングの集合は利用していない。
// キーワードだけで絞り込む [公開されている募集] ∩ [キーワードで絞り込んだ集合] // 地域とキーワードで同時に絞り込む [公開されている募集] ∩ ([北海道の募集] ∪ [関東の募集]) ∩ [キーワードで絞り込んだ集合]
4. パーソナライズ
ユーザーの行動��ータが十分に貯まったので、それを元にしてそれぞれのユーザーに合ったコンテンツを推薦したいとしよう。その場合、あるユーザーがそれぞれのコンテンツをどの程度好むのかの予測数値をスコアを持つような集合を定義する。この計算は大規模なログデータにアクセスする他のマイクロサービスが行っている可能性があるが、集合として抽象化できるという点は変わらない。
class PersonalizedSet < RedBlocks::Set def initialize(user_id) @user_id = user_id end def key_suffix @user_id end def get RecommendationService.get_personalized_ranker end end
ここで定義した PersonalizedSet の使い方は色々な可能性がある。ほぼ全ての募集に対してある程度妥当な予測数値が付けられるのであれば単独でスコア付けに利用できるかもしれないし、一部の募集に対してだけ予測数値が付いて他はゼロになっているような疎な集合であれば、0..1 に正規化した上で一般的な人気度をスコアに持つ集合と足し合わせて利用できるかもしれない。念のため定式化すると、次のようになる:
[公開されている募集] ∩ [パーソナライズされたスコアを持つ集合] ∩ (その他の絞り込み条件) // パーソナライズだけ [公開されている募集] ∩ [パーソナライズされたスコアを持つ集合] ∩ [一般的な人気度をスコアに持つ集合] ∩(その他の絞り込み条件)// パーソナライズ + 人気度
5. 全ての統合
ここまで、RedBlocks を用いて定義した集合を組み合わせることで、様々な機能を実現できることを見てきた。実際のサービスでは、これをユーザーの入力に応じて動的に行う必要がある。それを例えば次のような service クラスを作成することで行う。
class ProjectListingService def new(params, user_id: nil) # ... @set = build_set(params, user_id) end def fetch(per: 10, page: 1, format: :ids) paginator = build_paginator(per, page) case format when :ids then set.ids(paginator) when :count then set.size end end private def build_set(params, user_id) sets = params.each do |k,v| ... end sets << VisibleSet.new sets << PersonalizedSet.new(user_id) if user_id RedBlocks::IntersectionSet.new(sets, cache_time: 10.minutes) end end
ここで、新たに RedBlocks::IntersectionSet に cache_time オプションを追加していることに注意しよう。ここまで、RedBlocks::IntersectionSet や RedBlocks::UnionSet と言った合成型の集合についてそれがどの程度の期間���ャッシュされるのかについては言及していなかったが、実はデフォルトでは計算に必要な最低限の期間しか生存しない。しかしこのコードでは、絞り込みなどを行った上でのページングが再計算せず高速で行われるように、最終的な出力結果に対しては10分間のキャッシュを行うように設定している。
変更に対するモジュラ性の検討
さて、ここまでで RedBlocks をつかうことで一覧画面をどのように実装するかをおおよそ見ることができた。この実装方法について変更に対するモジュラ性の観点からどのような改善ができたのか、あり得る変更に対してどのように関心を分離できているかを明らかにしておきたい。
同様のロジックを持つ集合をクラスベースでまとめ上げることができる(例:地域クラス - 北海道インスタンス)
これにより、新たな地域を絞り込み項目に追加する際には、それを地域のリストに追加するだけで済むようになる
各サブシステムへのアクセスがクラスの get メソッドに抽象化される
これにより、サブシステムの置き換えと言った変更が get メソッドの実装に局所化されることになる
速度的な最適化は暗黙的なキャッシュにより行われる
これにより、遅いサブシステムにはキャッシュを入れる、と言ったロジックに関係のないコードが省略される
データを引く部分を Set クラス、統合を ProjectListingService で行うという分離ができている
これにより、「言語」と言った全く新しい絞り込み項目を追加する際も他の Set クラスは一切変更せずに済む
具体的には 1) 新規に LanguageSet クラスを定義, 2) Service クラスの変更 の2点を行えば良い
また、各集合のスコアをどのように利用するか、AND にするか OR にするかと言った変更の際は service クラスのみの変更で済む
これは次節で述べるが、Redis に関する様々な詳細実装を省略できる
これにより、本来のアプリケーション・ロジックのみが記述される
このように RedBlocks を適切に用いて実装することで、クラスベースのオブジェクト指向プログラミングの概念を利用して適切に関心を分離した形で、一覧画面を実装できることが分かった。
実装に関する詳細
先ほどの章では、RedBlocks を使うことで Redis をオブジェクト指向の枠組みで取り扱うことができることを説明した。この章では「課題2: 実装に必要な多くの詳細」についてより詳しく述べる。具体的にライブラリが裏で行っている仕事について、以下の6点を取り上げる。
キー体系
キャッシュとして動作させる
Redis 上のデータの生存期間
キャッシュを差分更新にする
空集合を表現する
集合の最適化
ここでは実装上の詳細について取り上げるため、特に全体としてまとまりがあるわけではない。興味のある話題だけを選択的に見てもらえれば幸いである。
キー体系
Redis を利用するには当然キーを指定する必要があるわけだが、このキーについて RedBlocks では次のような体系を用いている:
<全体の名前空間>:<クラスレベルの識別子>:<インスタンスレベルの識別子>
まずキーが3階層に分けれている。最初の階層は全体の名前空間を表していて、デフォルトで "RB" となる。二つ目の階層はクラスレベルの識別子を表していて、RedBlocks::Set を継承したクラスの名前がデフォルトで利用される。三つ目の階層はインスタンスレベルの識別子を表していて、主に key_suffix メソッドの実装を通じてプログラマが指定する。例えば、先ほど出てきた「北海道」地域にある募集の集合、次のようなキーが構築される。
r = RegionSet.new('hokkaido') r.key #=> "RB:RegionSet:hokkaido"
合成型の集合のキー
RedBlocks::UnionSet など合成型の集合の場合は、key_suffix の決定を自動で行う。ルールは、内包する全ての集合のキーをソートして | によって結合したものとなる。
r1 = RegionSet.new('hokkaido') r2 = RegionSet.new('kanto') RedBlocks::UnionSet.new([r1, r2]).key_suffix #=> "[RB:RegionSet:hokkaido|RB:RegionSet:kanto]" RedBlocks::UnionSet.new([r1, r2]).key #=> "RB:RedBlocks::UnionSet:[RB:RegionSet:hokkaido|RB:RegionSet:kanto]"
これによって、カスタムクラスを定義することなく簡単に和集合や積集合と言った合成集合を作ることができている。
キャッシュとして動作させる
キャッシュとして動作させる、つまり「無ければ元データを取得しキャッシュに乗せて次回以降はそれを使う」という動作を実現するために、次のようにデータ取得の際にキャッシュの有無をチェックしている。
module RedBlocks class Set def disabled? RedBlocks.client.ttl(key) < RedBlocks.config.intermediate_set_lifetime # この lifetime はデフォルトで30秒 end def ids(...) update! if disabled? ... end end end
このコードは、該当のインスタンスに相当する Redis の Sorted Set の生存期間がまだ30秒以上あれば有効であり、そうでなければ無効と判定してデータの更新を行っている(0ではなく30秒である理由は次で説明する)。
Redis 上のデータの生存期間
RedBlocks を通じて利用する Sorted Set はキャッシュとして一定期間 Redis 上に存在させることができるが、その一方で ZINTERSTORE(dest, src1, src2, ...) や ZUNIONSTORE(dest, src1, src2, ...) と言った演算を Redis 上で行うためには、キャッシュ期間をゼロに設定されている(つまり結果を返した直後に消滅して良い) Sorted Set であっても最低限存在しなければならない期間が存在する。
RedBlocks ではこれをデフォルトで30秒と定め、「(get メソッドを通じたデータの取得も含めて)ids を呼び出してから最終結果取得までの全ての工程が30秒以内に終了する」という仮定の元に正常に動作することを保証している。
以下に示す実装の通り、キャッシュとして存在する時間とは別で30秒は必ず存在するように全ての生存期間が設定されている。
module RedBlocks class Set def expiration_time RedBlocks.config.intermediate_set_lifetime + cache_time end def update! ... RedBlocks.client.expire(key, expiration_time) end end end
キャッシュを差分更新にする
キャッシュの更新の際、一度全てを削除してから新しくデータを入れるという方法だと、瞬間的にサービスにおけるコンテンツの件数がゼロになってしまう。一瞬であってもその状況は好ましくないため、実際のキャッシュの更新処理では更新の前後で削除された要素のみを明示的に Sorted Set から取り除いている(増分に関してはスコアのみが変わる可能性もあるため、全てを更新する必要がある)。
module RedBlocks class Set def update! ... RedBlocks.client.pipelined do RedBlocks.client.zrem(key, removed_ids) if removed_ids.size > 0 RedBlocks.client.zadd(key, all_entries) RedBlocks.client.expire(key, expiration_time) end end end end
空集合を表現する
Redis の Sorted Set の仕様では、内包する要素が0件であるという状態と、Sorted Set 自体が存在しないという状態を区別できない。しかし、RedBlocks のユースケースにおいては、「空の集合である」という状態もキャッシュしたい(例えば、特定のキーワードによる全文検索の結果が0であるということ自体もキャッシュしたい)。
このため、実際にコンテンツに対応する要素以外に、id が 0 の要素を常に加えるようになっている。この 0 は実在する要素の id として取り得ない値を想定しており、RedBlocks.config.blank_id を通じて別の値にも設定することができる。
集合の最適化
最後に、集合構造を最適化する RedBlocks::Set#unset について紹介する。
状況として、「職種」と「地域」と言った複数の観点での絞り込みができる仕様があるとする。例えば、次のようなクエリがあり得る:
# pattern 1 ([エンジニアの募集] ∪ [デザイナーの募集]) ∩ ([北海道の募集] ∪ [関東の募集] ∪ [東海の募集]) // pattern 2 [デザイナーの募集] ∩ [北海道の募集]
このとき、両者を同時に処理できるように一般的なプログラムを書くと、後者は次のような構造になる可能性が高い。
pattern1_set = RedBlocks::IntersectionSet.new(RedBlocks::UnionSet.new([engineer_set, designer_set]), RedBlocks::UnionSet.new([hokkaido_set, kanto_set, tokai_set])) pattern2_set = RedBlocks::IntersectionSet.new(RedBlocks::UnionSet.new([engineer_set]), RedBlocks::UnionSet.new([hokkaido_set]))
当然ながらこの集合構造は冗長であり、最初に示したようなクエリに変換できると良い。形式的に言って内部に1つの集合を持つような積集合はその内部の集合と等価であるため、このような冗長な構造を再帰的に最適化するために unset メソッドを用意している。この例では、以下のように動作する。
pattern2_set.unset #=> RedBlocks::IntersectionSet.new(engineer_set, hokkaido_set)
制限事項
以上、この節で紹介した実装詳細のうち、次の二つのアイデアについて制限事項を検討する。
Redis を演算とキャッシュの二つの目的で利用
Redis のキーの自動生成
1 により単純なシステム構成になり、2によりプログラミング上の些事が消失する。 これがクイックに低レイテンシーな検索・フィルタリングシステムを構築することに寄与する。 しかしながら、いくつかの状況においてはこのアプローチは適さないと思われる。
まず1については、Redis 上での演算を前提としているため、演算性能もまた Redis の上限に制約される。これは数十万件程度のデータが対象であば問題にならないが、数千万件以上のデータがある場合は性能上の問題が出る。このような規��に成長した場合は、演算の責務を Redis に担わせるのではなく別のシステムに担わせるのが妥当だろう。
次に2については、クラス名というプログラム上の実体からキーを生成しているため、それの変更に影響を受ける。これがキャッシュの Thundering Herd を引き起こす可能性が原理的に存在する。そのような非常に高並列性のシステムにおいては、演算をオンラインで行うのではなく、オフラインで行っておくなど、異なるアプローチを検討するべきだと思われる。
おわりに
本稿では、Redis の Sorted Set の Web サービスにおける活用方法として、一覧画面の実装での利用を挙げた。特に、マイクロサービス文脈では複数のシステムの「統合」に課題があることを指摘し、その基幹部分に Redis の Sorted Set を使うことを提案した。
その上で、それを現実的に保守可能な形で実装するために RedBlocks という抽象化を行い、実際のアプリケーションで求められる仕様をピックアップしながらそれを上手く記述できることのケーススタディを行った。このケーススタディの中では、HTTP サーバーではなく ActiveRecord を介してデータベースへ問い合わせるようなサンプルコードも掲載しているが、マイクロサービスの移行期には HTTP による通信とデータベースへの直接参照が混合するケースも多く、そういった部分も含めて現実的に使っている統合手法として紹介した。
また、RedBlocks については更に踏み込んで、アプリケーションのロジックを完結に記述できるようにするために裏にどのような仕事を隠蔽する必要があったかを紹介した。特に、複雑な Redis のデータ構造をどのようにクラス���ースのオブジェクト指向プログラミングに落とし込むか、それと関連してどのようにシステマチックにキーを管理するかというようなトピックは、他の Redis のデータ型に対しても適用できる可能性もあり、今後の発展が見込める。
0 notes
Text
Keep your pet safe with a custom engraved dog collar from CurliTail. Made in the USA, stylish, and built to last.
#dog collar#dogcollarbow#floralflowercollars#dogharness#leashset#personalizedset#dogcollar#flowercollars#curlitail
0 notes
Text
Discover premium personalized leather dog collars at CurliTail. Durable, stylish, and custom-engraved for your pet’s comfort and safety. Shop now! https://curlitail.com/
0 notes
Text
Explore our premium floral flower collars, designed for comfort and beauty. Perfect for dogs and cats. Find your pet’s new look today!
#curlitail#dog collar#dogcollar#dogharness#dogcollarbow#floralflowercollars#flowercollars#leashset#personalizedset#girldogcollar
0 notes
Text

Get a customized dog collar & leash set with your pet’s name & details. Stylish, durable, & comfortable—shop the best personalized sets at CurliTail! Visit https://curlitail.com/
#curlitail#dog collar#dogcollarbow#dogharness#floralflowercollars#leashset#personalizedset#flowercollars#dogcollar#girldogcollar
0 notes
Text
Get the best-personalized dog mom-dad sweatshirts & t-shirts in the USA. Soft, stylish, and made with love—perfect for proud pet parents!
#dog collar#dogcollarbow#curlitail#flowercollars#floralflowercollars#leashset#personalizedset#dogharness#dogcollar#girldogcollar
0 notes
Text
Top 5 Benefits of Personalized Dog ID Collars and Leashes for Your Furry Friend
Every pet owner wants to ensure their furry friend is safe, comfortable, and stylish. Personalized dog ID collars and leashes are a great way to achieve this while providing essential identification in case your pet gets lost. Here are the top five benefits of investing in a customized collar and leash for your dog.
Enhanced Safety and Quick Identification
One of the most significant advantages of a personalized dog ID collar is the immediate identification it provides. If your dog ever wanders away, a collar with their name and your contact information makes it easier for someone to return them quickly. Unlike ID tags that can fall off or fade over time, personalized collars keep essential details securely in place.
Stylish and Unique Design
Custom collars and leashes allow pet owners to showcase their dog's personality. Whether you prefer a sleek, elegant design or something colorful and fun, you can choose fonts, colors, and materials that reflect your pet’s unique style. Personalized options also make it easy to distinguish your dog’s accessories from others at the park or doggy daycare.
Comfort and Durability
Many personalized dog ID collars and leashes are made from high-quality materials such as nylon, leather, or waterproof fabric, ensuring durability and long-lasting comfort. Adjustable sizes and soft linings help prevent chafing, making them comfortable for daily wear. Investing in a high-quality custom collar means less frequent replacements and better security for your pet.
Helps in Emergency Situations
In case of an emergency, a personalized collar can provide crucial information. Some pet owners include additional details like medical conditions, allergies, or special needs on their dog’s collar. This ensures that if your pet is found by someone else, they are aware of any health concerns that require immediate attention.
Conclusion
Personalized dog ID collars and leashes are more than just stylish accessories—they are essential tools that enhance your pet's safety, comfort, and identity. With benefits ranging from quick identification to durability and unique designs, investing in a customized collar and leash is a smart decision for any pet owner. Give your furry friend the protection they deserve while adding a touch of personality to their daily walks!
0 notes
Text

Pamper your pet with Personalized navy velvet dog bed. Stylish, cozy, and personalized for your dog’s comfort. Made in the USA. Visit https://curlitail.com/
0 notes
Photo

Pretty in Pink Knit Choker ✨Shop Wild Child✨ Link in Bio⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ #WildChildCreations #knitchoker #chokernecklace #spookycute #spookyjewlery #modernpinup #creepyshop #alternativegirl #softgoth #Cutekawaii #stayspooky #giftsforfriends #pinupgirl #personalizeditems #personalizedkeychain #kawaiiaesthetic #personalizedgifts #pastelgoth #kawaiistyle #pinkaesthetic #kawaiiaccessories #custommade #spookystyle #witchyaesthetic #prettyinpinkblush #personalizedset #cottagecore #smallshoplove #prettyinparisian #womenownedsmallbusiness https://www.instagram.com/p/CJ6dfv6gWex/?igshid=1b2c0661di3cc
#wildchildcreations#knitchoker#chokernecklace#spookycute#spookyjewlery#modernpinup#creepyshop#alternativegirl#softgoth#cutekawaii#stayspooky#giftsforfriends#pinupgirl#personalizeditems#personalizedkeychain#kawaiiaesthetic#personalizedgifts#pastelgoth#kawaiistyle#pinkaesthetic#kawaiiaccessories#custommade#spookystyle#witchyaesthetic#prettyinpinkblush#personalizedset#cottagecore#smallshoplove#prettyinparisian#womenownedsmallbusiness
0 notes
Photo

No one can stop you from trying ✨Shop Wild Child✨ Link in Bio⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ #WildChildCreations #staymotivated #strongwomen #spookycute #spookyjewlery #modernpinup #creepyshop #alternativegirl #softgoth #Cutekawaii #stayspooky #giftsforfriends #pinupgirl #personalizeditems #personalizedkeychain #kawaiiaesthetic #personalizedgifts #pastelgoth #kawaiistyle #pinkaesthetic #kawaiiaccessories #custommade #spookystyle #witchyaesthetic #losangeles #personalizedset #kindnessmatters #smallshoplove #newyearnewgoals #womenownedsmallbusiness (at Los Angeles, California) https://www.instagram.com/p/CJZH_u3ARzv/?igshid=bfiuxstxrxl6
#wildchildcreations#staymotivated#strongwomen#spookycute#spookyjewlery#modernpinup#creepyshop#alternativegirl#softgoth#cutekawaii#stayspooky#giftsforfriends#pinupgirl#personalizeditems#personalizedkeychain#kawaiiaesthetic#personalizedgifts#pastelgoth#kawaiistyle#pinkaesthetic#kawaiiaccessories#custommade#spookystyle#witchyaesthetic#losangeles#personalizedset#kindnessmatters#smallshoplove#newyearnewgoals#womenownedsmallbusiness
0 notes
Photo

Rose Hoop Earrings 🌹 Available in Shop now 🤍 ✨Shop Wild Child✨ Link in Bio⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ .⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ #WildChildCreations #roseearrings #spookycute #spookyjewlery #modernpinup #hoopearrings #alternativegirl #halloweeniseveryday #softgoth #Cutekawaii #stayspooky #giftsforfriends #pinupgirl #personalizeditems #personalizedkeychain #kawaiiaesthetic #personalizedgifts #pastelgoth #kawaiistyle #pinkaesthetic #kawaiiaccessories #custommade #spookystyle #witchyaesthetic #animevibes #personalizedset #latinavibes #roseacccesaories #shopsmallbusinesses #rosejewlery (at Los Angeles, California) https://www.instagram.com/p/CJWTbdagKZf/?igshid=5ggvq0bkb705
#wildchildcreations#roseearrings#spookycute#spookyjewlery#modernpinup#hoopearrings#alternativegirl#halloweeniseveryday#softgoth#cutekawaii#stayspooky#giftsforfriends#pinupgirl#personalizeditems#personalizedkeychain#kawaiiaesthetic#personalizedgifts#pastelgoth#kawaiistyle#pinkaesthetic#kawaiiaccessories#custommade#spookystyle#witchyaesthetic#animevibes#personalizedset#latinavibes#roseacccesaories#shopsmallbusinesses#rosejewlery
0 notes