iOS版浏览器如何改进A / B测试。 Yandex报告

不久前,我们研究了搜索中A / B实验的安排方式。 iOS版Yandex开发团队的负责人。俄罗斯CocoaHeads上次会议上的浏览器Andrei Sikerin sav42也谈到了A / B测试基础架构,仅在他的项目中。



-嗨,我叫Andrey Sikerin,我正在开发iOS的Yandex.Browser。 我想告诉您iOS的浏览器实验平台是什么,我们如何学习使用它,如何支持其更高级的功能,如何使用实验系统推出的诊断和调试功能,以及熵的来源以及在何处硬币被存储。

因此,让我们开始吧。 我们在iOS浏览器中永远不会向用户滚动功能。 首先,我们进行A / B测试,分析产品和技术指标,以了解滚动功能如何影响用户,无论用户是否喜欢,是否浪费一些技术指标。 为此,我们使用分析。 我们的分析看起来像这样:



大约有85个指标。 我们比较了几个用户组。 假设这增加了我们的指标(例如,该产品保留用户(保留)的能力),并且不会浪费幻灯片中未显示的其他指标。 这意味着用户喜欢该功能,可以将其扩展到一大批用户。

但是,如果我们浪费了一些东西,那么我们就会明白原因。 我们建立假设,确认它们。 如果我们绘制技术指标-这是一个障碍。 我们修复了它们,然后再次重新运行实验。 直到我们画完所有东西。 因此,我们推出了不是回归源的功能。

让我们谈谈我们使用的原始实验系统。 她已经很发达了。 那我告诉你什么不适合我们。



首先,它基于Chromium实验系统,iOS上未完全支持它。 其次,它最初能够将功能推出给不同的用户组,并具有一个可以设置设备要求的过滤器系统。 也就是说-可以使用该功能的应用程序版本,设备语言环境-假设我们只想针对俄语语言环境进行实验。 可以使用此功能的iOS版本,或该实验有效的日期-例如,如果我们只想进行某个实验直到某个日期。 通常,标签很多,很方便。

实验系统本身包含一个文件,其中包含有关实验配置的描述。 也就是说,对于一个实验,一次可以有多个配置。 该文件是一个文本文件,它被编译成protobuf并放在服务器上。

每个配置由组组成。 有一个实验,它有多个配置,每个配置中都有几个组。 代码中的功能附加到活动配置的活动组的名称上。 它看起来似乎很复杂,但是现在我将详细解释它的含义。



从技术上讲它如何工作? 带有所有配置描述的文件被上载到服务器。 启动时,浏览器从服务器下载该文件并将其保存到磁盘。 下次启动时,我们将在应用程序初始化链中的第一个位置解码该文件。 对于每个独特的实验,我们都会找到一个可以激活的配置。

适用于其中指定和描述的条件的配置可以生效。 如果存在多个符合指定条件的活动配置,则将激活文件中较高的配置。

此外,在主动配置中,投掷硬币。 硬币会在本地投掷,然后按照某种方式根据该硬币进行投币,稍后我将进行讨论,选择实验的活动组。 正是在代码中附加了实验的活动组的名称,检查我们的功能是否可用。

该系统的关键功能是它本身不会存储任何内容。 也就是说,她在磁盘上没有任何存储。 每次启动-我们获取文件,开始计算它,找到活动的配置。 在配置内部,根据硬币,我们找到了活动组,该实验的实验系统显示:已选择该组。 也就是说,一切都计算了,什么也没有存储。



实际上,让我为您展示一个包含实验说明的文件。 浏览器具有这样的功能-翻译器。 她在实验中推出。 该文件以学习块开头。 任何实验的配置都从该块开始。 该实验称为翻译器。 这个名字可能有几个这样的学习区。 在学习区中,有许多实验区被分配了不同的名称。 在这种情况下,我们看到实验组已启用。 并且有一个过滤器块,实际上描述了该配置在什么条件下可以生效,即其标准。

这里有两个标签-channel和ya_min_version。 通道表示装配体视图。 BETA在此处指示,这意味着文件中的此配置仅对我们发送给TestFlight的那些程序集有效。 对于App Store构建,无法通过渠道标准进行此配置。

ya_min_version意味着,使用最低版本的应用程序19.3.4.43,此配置可以激活。 实际上,在此版本的应用程序中,该功能已经获得了可以启用它的形式。

这是翻译器实验配置组的最简单描述。 一个文件中可以有许多这样的学习块。 使用过滤器块中的标签,我们将它们设置为不同的通道,内部装配体,BETA装配体以及各种条件。

这是一个称为启用的实验组,它具有一个概率权重标签,即实验组的权重。 这是一个非负整数,用于确定硬币弹出时的活动组。

假设幻灯片上的此配置已激活。 也就是说,我们确实使用公开Beta版安装了该应用程序,并且确实具有19.3.4.43版以上的版本。 如何抛硬币? 硬币是从零到一本地生成的随机数。

因此,在下次启动期间,我们属于同一组,它存储在磁盘上。 虽然我们会考虑。 接下来,我将告诉您如何确保未存储该文件。 硬币被扔掉了。 假设抛出0.5。 该硬币按从零到实验组总和的比例缩放。 在这种情况下,我们有一个启用的组,其权重为1000,即所有组的总和为1000。“ 0.5”缩放为500。因此,所有实验组将间隔从零除以实验量和间隔。 并且该组变得活跃,在该间隔内将显示硬币的比例值。

我们可以在代码中询问活跃实验组的名称,从而确定可访问性-是否需要启用该功能。



此外,我们将研究在生产中使用的更复杂的实验配置。 首先,很明显,将功能推出100%是愚蠢的,我们仅将此功能用于Beta或内部组装。 对于生产,我们使用以下机制。

我们将用户分为三类-老用户,没有实验的用户和新用户。 在意义上,这意味着以下含义。 旧用户是指已经使用我们的应用程序并安装了具有旧版本功能的应用程序的用户。 也就是说,他们已经使用过它,没有功能,已经习惯了一切并突然更新了应用程序,其中包含某种实验,新功能。 然后-没有实验的用户和新用户。 新的是那些使应用程序干净的应用程序。 也就是说,他们从未使用过Yandex.Browser,而是突然决定使用它并安装了该应用程序。

我们如何实现这种划分? 在过滤器块中,我们设置min_install_date和max_install_date标记的条件。 假设X是2019年3月14日-这是功能部件构建的发布日期。 然后,具有功能的程序集发布之前,旧用户的max_install_date将是X减去21天。 如果应用程序具有这样的安装日期,则很有可能其首次启动是在发行之前。 在发行之前,有一个没有功能的版本。 如果现在他有条件地拥有一个带有功能的版本,则意味着他是在更新帮助下收到该应用程序的。

对于新用户,我们设置min_install_date。 我们将其公开为X加几天。 这意味着:如果他具有这样的安装日期,即他在具有功能的版本发布日期之后进行了首次启动,则他具有全新安装。 他现在有一个具有功能的版本,但是安装日期晚于具有功能的该版本。

因此,我们将用户分为旧用户,而没有实验用户和新用户。 我们这样做是因为我们看到:旧用户的行为不同于新用户的行为。 因此,例如,我们可以不与旧用户一起绘画,而与新用户一起绘画,反之亦然。 如果我们对整体进行实验,则可能看不到这一点。



让我们看一下这个实验。 我们看到以下实验配置-App Store的翻译者,新用户。 阻止学习,姓名翻译,组enabled_new。 前缀新意味着我们为许多新用户描述了配置。 权重500(如果所有权重之和为1000,则此组的功效为50%)。 Control_new,权重500,这是第二组。 最有趣的是STABLE通道的过滤器,即为生产而组装的程序集的过滤器。 该功能出现的版本:19.4.1。 这是min_install_date标记。 在这里,它以Unix时间格式在2019年4月18日加密。 这是在19.4.1版发布后的几天。

除了新的前缀,这里还有另外一部分,它是启用和控制的。 这是控件前缀;这不是偶然的。 除了我们将用户分为新老用户外,我们还在实验中将他们分为几部分。



用户的第一部分是一个控制组,它具有控制前缀。 没有任何功能。 她的权重为X,也有一个功能组,通常称为启用。 它还具有X权重,这很重要:应该打开该功能。 并且有一个默认组,其权重为1减去2倍(1000减去2倍,因为1000是同一配置内所有组的总权重值,默认情况下会接受)。 默认组也不包含任何功能。 它只是存储了分成控制权和功能权后仍留存的用户。 如有必要,您还可以从中重新运行实验。



比方说,老用户的配置。 我们将在此处看到功能部件和控制组。 enabled_old-特色。 control_old,-控制,10%。 default_old-默认值为80%。

注意过滤器,ya_min_version 19.4.1,max_install_date 2019年3月28日。 此日期早于发布日期。 因此,这是一个配置,其中包含在更新后收到版本19.4.1的用户的列表。 他们使用了该应用程序,现在使用了新版本。

为什么需要功能和控制组? 在第一张幻灯片中显示的分析中,我们比较了对照组和功能组。 他们必须具有同等的权力,以便可以比较他们的产品指标。

因此,我们在分析中比较了新旧用户组中的控件组和功能组。 如果绘制所有内容,则将特征滚动100%。



代码开发人员如何在此系统上工作? 他知道功能部件组的名称,需要启用功能部件的日期,并编写了一些访问层(这是一个伪代码),以实验名称请求一个活动组。 可能不是。 实际上,所有配置可能都不适合设备的条件。 然后,空字符串将返回。

此后,如果具有活动组的名称,则需要启用该功能,否则将其关闭。 此外,此功能已在代码中使用,其中包括浏览器代码中的功能。



因此,我们在这种实验系统上生活了几年。 一切都很好,但显示出许多缺点。 这种方法的第一个缺点是,如果不更正代码,就不可能添加新的实验组。 也就是说,如果实验的名称不太可能针对某个功能进行更改,则可以添加更多的其他组,这很容易。 但是您的功能可访问性代码不知道这样的组,因为您没有预先预见到它。 因此,您需要滚动该版本,尝试使用该版本,这是一个问题。 也就是说,有必要通过更改代码来重建并发布到App Store中。

其次,在开始实验后,您无法展开功能的一部分或将功能分解为多个部分。 也就是说,如果您突然决定可以推出某些功能,而某些功能仍留在实验中,那么您就无法做到这一点,您必须事先考虑一下将此功能分为两部分,并在实验中独立接受。

第三,您无法配置功能或比较配置。 例如,在Translator中,有一个参数-Translator API的超时时间。 也就是说,如果我们在几毫秒内都无法翻译,那么我们就说,再试一次,这是一个错误,没有运气。

无法在实验中设置此超时时间,因为我们要么需要立即修复组,要么让我们提前设置以下组-enabled_with_300_ms,enabled_with_600_ms,其名称中对参数值进行了编码。 但是以某种方式无法通过数字设置参数。 如果我们之前从未考虑过,那么我们将无法再比较几种配置。

第四,分析师和开发人员被迫提前就组名称达成一致。 也就是说,实际上,为了使开发人员开始开发功能,他通常从该功能的可用性策略开始。 而且他需要知道要素组的名称。 为此,分析人员必须说明实验的机制-我们是将用户分为新用户还是旧用户,还是所有用户都属于同一组而不进行划分。

否则可能是反向实验。 例如,我们可以立即考虑启用该功能,但可以将其关闭。 对于分析人员来说,这不是很有趣,因为该功能尚未就绪。 准备就绪后,他将确定实验的机制。 开发人员需要事先提供组的名称和实验机制,否则他将不得不不断更改代码。

我们进行了协商,并确定这足以承受。 因此,“使实验又一次伟大”项目诞生了。



该项目的关键思想如下。 如果之前我们附加了代码,即代码传递给分析师传递给我们的活动组的名称,那么现在我们添加了两个附加实体。 此功能(功能)和功能参数(FeatureParam)。 因此,程序员可以独立地发明特征和特征参数,为它们选择标识符,为它们选择默认值,并为它们的功能编程。

之后,他将这些标识符传递给分析人员,然后分析人员仔细考虑实验的机制,然后使用feature_association标签在实验组中以特殊方式指定它们。 如果该组处于活动状态,则请记住使用诸如此类的标识符启用或禁用该功能,并使用此类标识符设置参数。



实验配置文件中是什么样的? 这里我们来看实验组。 启用名称,在此enable_feature或disable_feature命令标记中添加可选的feature_association标记,并添加标识符。

还有一个参数块,其中可以有多个。 这里还有一个名称-超时,并添加了需要设置的值。



从代码来看这是什么样子? 程序员声明Feature和FeatureParam类的实体。 并将这些原语中的值写入功能访问层。 然后,它将这个标识符传递给分析人员,并且它已经在配置文件中使用feature_association标记在实验组的块中设置了标识符。 一旦实验组启用,就会从文件中设置代码中具有这些标识符的特征和参数的值。 如果组中没有参数和功能,则使用默认值,这些默认值从代码中指示出来。

看来这给了我们? 首先,添加新组时,分析人员无需要求程序员将新功能组添加到代码中,因为数据访问层使用的标识符在将新组添加到实验系统时不会更改。

其次,我们发布了程序员为特征和特征参数提供这些标识符的时间,以及分析人员开发实验机制的时间。 分析人员在功能准备就绪时进行开发,而程序员在编写代码时就从一开始就想到了这些标识符。

它还允许您将功能分解为多个部分。 假设有一个称为Translator的功能,实际上包括Translator。 并且具有TranslateServiceAPITimeout的功能,它包括可以为Translator API设置自定义超时的其他功能。 因此,我们可以进行两组实验,在这两组实验中都开启了翻译程序,但同时我们比较了哪个值更好:300毫秒或600毫秒。

. . (FeatureParam).

, , , . , , . , , . . ?



, : Feature FeatureParam. Feature FeatureParam . , Feature FeatureParam, , . . - , , .

-, Feature&FeatureParam. , «», , , . FeatureParam , , API — 300 600 ?

. . - , . public beta, . , .

, : .



? , , .

: . URL, , .

: browser://version — show-variations-cmd. : cheat-, . : .



. , . proto- - , study-, . , . Feature&FeatureParam, . , , . , , Feature&FeatureParam .

. proto- . . , . , , .



第二个。 Feature&FeatureParam? Chromium, . Chromium browser://version, show-variations-cmd.

: enabled-features, force-fieldtrials force-fieldtrials-params, . , . ? , . , Feature1 trial1. Feature2 trial2. Feature3 .

trial1 group1. trial2 group2. force-fieldtrials-params, , trial1 group1, p1 v1, p2 v2. trial2 group2, p3 v3, p4 v4.

, . Chromium, iOS. , .

. --force-fieldtrials=translator/enabled_new/ enabled_new translator.

--force-fieldtrial-params==translator.enablew_new:timeout/5000, translator enabled_new, , , translator, enabled_new timeout, 5 000 .

--enabled-features=TranslateServiceAPITimeout<translator , - translator translator , , , TranslateServiceAPITimeout. , , , , .



(cheat urls). , , , , , , . . . .

yandexbrowser:// (.), , . . my_pet_experiment=group_name. , enable-features=, , disable-features=, . , &.

(cheat url), . , , , . , , . filter, my_pet_experiment , . 1000, feature_association, , .

, . , , . , — my_pet_experiment — , , . , , study .



, . — , . . , .

, .



, , , , . , . , .

. . , , ? , . , ?

. .



, . , , UUID, application Identifier, . . hash .

? ? , , UUID, . ? , , . . ? hash hash , . , Google, An Efficient Low-Entropy Provider.

, — UUID, , , . , , Chromium.

, , ? ? :

  1. : , -. .
  2. . , , , .
  3. 可测试的。您必须具有允许重新定义组,要素,参数或其他所需实体的值的机制。
  4. 一种将不同的原语用于分析和编程的地方。
  5. 可扩展 您应该能够看到它的工作原理并使其适应您的需求(请参阅Chromium变异服务)。

我们在Yandex.Browser for iOS中扩展的Chromium系统具有这样的标准。进行实验,对其进行分析并改善应用程序。谢谢啦

Source: https://habr.com/ru/post/zh-CN475012/


All Articles