{"id":77367,"date":"2019-06-10T09:00:29","date_gmt":"2019-06-10T16:00:29","guid":{"rendered":"https:\/\/cloudblogs.microsoft.com\/opensource\/?p=77367"},"modified":"2025-06-27T06:44:06","modified_gmt":"2025-06-27T13:44:06","slug":"how-to-use-trill-impression-feedback-part-2","status":"publish","type":"post","link":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/","title":{"rendered":"How to use Trill for impression feedback (part 2)"},"content":{"rendered":"\n<p>This is part 2 of 2-post series that shows you how to use Trill, an open source .NET library designed to process one trillion events a day, for impression feedback.<\/p>\n\n\n\n<p>In&nbsp;<a href=\"https:\/\/cloudblogs.microsoft.com\/opensource\/2019\/05\/29\/how-to-use-trill-for-impression-feedback-part-1\/\">part 1<\/a>, we walked through how to write Trill queries to find out: 1) which impressions successfully joined to the feedback stream and 2) which impressions never joined to the feedback stream (expired). Today, we\u2019ll walk through a final step \u2014 how to find out impressions immediately upon arrival, whether joined with feedback or not.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"3-a-stream-of-impressions-immediately-upon-arrival-whether-joined-or-not\">3. A stream of impressions immediately upon arrival, whether joined or not<\/h2>\n\n\n\n<p><em>(Note: step 2 and 3 were covered in part 1, which you can find&nbsp;<a href=\"https:\/\/cloudblogs.microsoft.com\/opensource\/2019\/05\/29\/how-to-use-trill-for-impression-feedback-part-1\/\">here<\/a>.)<\/em><\/p>\n\n\n\n<p>Compared to the previous case, this one is easy \u2013 an antijoin sounds perfect:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ninternal static IStreamable JoinFeedbackToImpressions(\n    this IStreamable impressions,\n    IStreamable feedback)\n{\n    \/\/ set the lifetime of the input streams\n    impressions = impressions.SetDuration(TimeSpan.FromMinutes(40));\n    feedback    = feedback   .SetDuration(TimeSpan.FromMinutes(30));\n\n    \/\/ produce a stream of successful joins\n    IStreamable joined = impressions\n        .Join(\n            feedback,\n            imp  => imp.RGUID,\n            fdbk => fdbk.RGUID,\n            (imp, fdbk) => new ImpressionsWithFeedback(imp, fdbk))\n        .ToPointStream()\n        ;\n\n    \/\/ produce a stream of \"expired\" point events that coincide with the end time\n    \/\/ of the impression stream, and suppress those which had a successful join\n    IStreamable expired = impressions\n        .PointAtEnd()\n        .LeftAntiJoin(\n            joined.SetDuration(TimeSpan.FromMinutes(40)),\n            l => l.RGUID,\n            r => r.RGUID)\n        .Select(imp => new ImpressionsWithFeedback(imp, null))\n        ;\n+\n+   \/\/ produce a stream of failed joins\n+   IStreamable unjoined = impressions\n+       .LeftAntiJoin(\n+           feedback,\n+           imp  => imp.RGUID,\n+           fdbk => fdbk.RGUID)\n+       .Select(imp, => new ImpressionsWithFeedback(imp, null))\n+       ;\n\n-   \/\/ union together the two streams and return the result\n-   return joined.Union(expired); \n+   \/\/ union together the three streams and return the result\n+   return joined.Union(unjoined).Union(expired);\n}\n<\/pre><\/div>\n\n\n<p>Great!&nbsp; So now we\u2019re, done, right?<\/p>\n\n\n\n<p>Of course not.&nbsp; (What fun would that be?)&nbsp; Let\u2019s again look at what we are now outputting.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/06\/Trill-part-2_image-1.png\" alt=\"Trill impressions diagram\" \/><\/figure>\n\n\n\n<p>There are three of problems with the above solution:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>When an impression arrives before its feedback (the\u00a0<em>After<\/em>\u00a0and\u00a0<em>Way after<\/em>\u00a0cases), we will output two events \u2013 the unsuccessful join followed by the successful join. The downstream statistics computation query must either count both events, which will result in double-counting, or discard both events, which will produce no result at all.<\/li>\n\n\n\n<li>If an impression never joins with feedback (the\u00a0<em>No feedback<\/em>\u00a0case), a similar situation occurs; two events are output, the unsuccessful join followed by the expired event. This causes the same issue in statistics and an additional, similar problem in the final output \u2013 we can\u2019t tell which event to use since we don\u2019t know whether the event is done receiving feedback or not.<\/li>\n\n\n\n<li>If feedback arrives less than 10 minutes after the impression (the\u00a0<em>Coincident<\/em>,\u00a0<em>Before<\/em>, and\u00a0<em>Way before<\/em>\u00a0cases), the antijoin will produce a second, \u201ctail antijoin\u201d event that we don\u2019t want to include in stats and which is not complete. These are the events outlined with a red dashed line in the above chart.<\/li>\n<\/ol>\n\n\n\n<p>We need some way to signal to our downstream operators whether a given event should contribute to statistics and whether it is done receiving feedback. Let\u2019s consider each case separately.<\/p>\n\n\n\n<p>First, let\u2019s examine the case of contributing to statistics. Remember that the requirement for statistics is that an impression must be counted exactly once, and it must be counted when it arrives. Although this doesn\u2019t sound too bad, we can express this requirement even more simply: an impression may only be counted if it\u2019s sync time upon output is equal to its sync time in the input.<\/p>\n\n\n\n<p>This is much easier \u2013 all that we need is the ability to get an event\u2019s sync time.&nbsp; Luckily, there is an operator which does exactly that \u2013 an overload of Select which accepts a delegate that performs a transformation on the payload and sync time of each event in a stream.<\/p>\n\n\n\n<p>Now let\u2019s consider the second problem, whether an impression should be considered done. An impression must be output as soon as it receives feedback, or once it expires if feedback never arrives.&nbsp; This case is more straightforward than the previous one since each of the internal streams is either always done or always not done.&nbsp; In this case, we can just pass true or false to the ImpressionsWithFeedback constructor to differentiate.<\/p>\n\n\n\n<p>Finally, the third case (extra tail antijoin events) can be addressed by filtering out events from the unjoined stream whose final sync time does not match its original sync time, very similar to how ContributeToStats is computed.<\/p>\n\n\n\n<p>After making these changes, let\u2019s see how our query looks.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ninternal static IStreamable JoinFeedbackToImpressions(\n    this IStreamable impressions,\n    IStreamable feedback)\n{\n-   \/\/ set the lifetime of the input streams\n-   impressions = impressions.SetDuration(TimeSpan.FromMinutes(40));\n+   \/\/ set the lifetime of the input streams, record the input sync time of the\n+   \/\/ events in the impressions stream\n+   impressions = impressions\n+       .SetDuration(TimeSpan.FromMinutes(40))\n+       .Select((t, x) => { x.OriginalSyncTime = t; return x; })\n+       ;\n+\n    feedback = feedback.SetDuration(TimeSpan.FromMinutes(30));\n\n    \/\/ produce a stream of successful joins\n    IStreamable joined = impressions\n        .Join(\n            feedback,\n            imp  => imp.RGUID,\n            fdbk => fdbk.RGUID,\n-           (imp, fdbk) => new ImpressionsWithFeedback(imp, fdbk))\n+           (imp, fdbk) => new ImpressionsWithFeedback(imp, fdbk, done: true))\n        .ToPointStream()\n        ;\n\n    \/\/ produce a stream of \"expired\" point events that coincide with the end time\n    \/\/ of the impression stream, and suppress those which had a successful join\n    IStreamable expired = impressions\n        .PointAtEnd()\n        .LeftAntiJoin(\n            joined.SetDuration(TimeSpan.FromMinutes(40)),\n            l => l.RGUID,\n            r => r.RGUID)\n-       .Select(imp => new ImpressionsWithFeedback(imp, null))\n+       .Select(imp => new ImpressionsWithFeedback(imp, null, done: true))\n        ;\n\n-   \/\/ produce a stream of failed joins\n+   \/\/ produce a stream of failed joins, dropping the tail join if present\n    IStreamable unjoined = impressions\n        .LeftAntiJoin(\n            feedback,\n            imp  => imp.RGUID,\n            fdbk => fdbk.RGUID)\n-       .Select(imp, => new ImpressionsWithFeedback(imp, null))\n+       .Select(imp, => new ImpressionsWithFeedback(imp, null, done: false))\n+       .Where((t, x) => x.OriginalSyncTime == t)\n        ;\n\n-   \/\/ union together the three streams and return the result\n-   return joined.Union(unjoined).Union(expired);\n+   \/\/ union together the three streams, mark records which should contribute\n+   \/\/ to stats, and return the result\n+   return joined\n+       .Union(unjoined)\n+       .Union(expired)\n+       .Select((t, x) =>\n+           {\n+               x.ContributeToStats = x.OriginalSyncTime == t;\n+               return x;\n+           })\n+       ;\n}\n<\/pre><\/div>\n\n\n<p>Astute readers will notice that filtering out the tail antijoins is not strictly necessary, since these events will neither contribute to statistics (their original sync time is not equal to their final sync time) nor be considered done (antijoin events are incomplete by definition).<\/p>\n\n\n\n<p>The reason to remove them here is to reduce the total number of events in the stream. Although they have no effect semantically, they still contribute to I\/O costs related to checkpointing and cross-node communication, as well as they use memory in any stateful operator that they flow through.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-final-query\">The final query<\/h2>\n\n\n\n<p>Phew! It was a long journey, but we are finally done!&nbsp; Let\u2019s admire the fruits of our labor.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ninternal static IStreamable JoinFeedbackToImpressions(\n    this IStreamable impressions,\n    IStreamable feedback)\n{\n    \/\/ set the lifetime of the input streams, record the input sync time of the\n    \/\/ events in the impressions stream\n    impressions = impressions\n        .SetDuration(TimeSpan.FromMinutes(40))\n        .Select((t, x) => { x.OriginalSyncTime = t; return x; })\n        ;\n \n    feedback = feedback.SetDuration(TimeSpan.FromMinutes(30));\n\n    \/\/ produce a stream of successful joins\n    IStreamable joined = impressions\n        .Join(\n            feedback,\n            imp  => imp.RGUID,\n            fdbk => fdbk.RGUID,\n            (imp, fdbk) => new ImpressionsWithFeedback(imp, fdbk, done: true))\n        .ToPointStream()\n        ;\n\n    \/\/ produce a stream of \"expired\" point events that coincide with the end time\n    \/\/ of the impression stream, and suppress those which had a successful join\n    IStreamable expired = impressions\n        .PointAtEnd()\n        .LeftAntiJoin(\n            joined.SetDuration(TimeSpan.FromMinutes(40)),\n            l => l.RGUID,\n            r => r.RGUID)\n        .Select(imp => new ImpressionsWithFeedback(imp, null, done: true))\n        ;\n\n    \/\/ produce a stream of failed joins, dropping the tail join if present\n    IStreamable unjoined = impressions\n        .LeftAntiJoin(\n            feedback,\n            imp  => imp.RGUID,\n            fdbk => fdbk.RGUID)\n        .Select(imp, => new ImpressionsWithFeedback(imp, null, done: false))\n        .Where((t, x) => x.OriginalSyncTime == t)\n        ;\n\n    \/\/ union together the three streams, mark records which should contribute\n    \/\/ to stats, and return the result\n    return joined\n        .Union(unjoined)\n        .Union(expired)\n        .Select((t, x) =>\n            {\n                x.ContributeToStats = x.OriginalSyncTime == t;\n                return x;\n            })\n        ;\n}\n<\/pre><\/div>\n\n\n<p>And here is the same query visualized using a directed-acyclic graph (DAG) of query operators:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/06\/Trill-part-2_image-2-839x1024.png\" alt=\"diagram of directed-acyclic graph (DAG) of query operators\" \/><\/figure>\n\n\n\n<p>Finally, let\u2019s go back to our favorite chart one last time to make sure we\u2019re producing the right thing. Whether an impression received feedback and the state of the two Boolean conditions (contribute to statistics, is done) are represented by the Joined, Stats, and Done monikers, respectively, preceded by \u2018+\u2019 and written in green for true and preceded by \u2018-\u2018 and written in red for false.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/06\/Trill-part-2_image-3.png\" alt=\"Trill impression diagram\" \/><\/figure>\n\n\n\n<p>Yep, that looks perfect!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"wrapping-up\">Wrapping up<\/h2>\n\n\n\n<p>We have finally produced our user-defined join operator for associating feedback with impressions, keeping in mind the requirements of our downstream operators.&nbsp; Let\u2019s reexamine the original stated requirements and briefly summarize how we addressed each one.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><em>Output impressions as soon as they join successfully (but not before):<\/em>\u00a0We use an inner join to output impressions as soon as feedback arrives. An explicit signal in the event indicates that the event is done and ready to be output.<\/li>\n\n\n\n<li><em>Don\u2019t output an impression more than once:\u00a0<\/em>Events are included in the output only if the join succeeds or if the event ends (expires) before joining, which are mutually exclusive cases.<\/li>\n\n\n\n<li><em>Don\u2019t drop any impressions:<\/em>\u00a0If feedback is never received for an impression, we output an explicit expired event at the end of the impression\u2019s lifetime. An explicit signal in the event indicates that the event is done and ready to be output.<\/li>\n\n\n\n<li><em>Output impressions immediately as they arrive so they can contribute to statistics:<\/em>\u00a0We include an antijoin as well as an inner join \u2013 the antijoin produces events for the case where feedback arrives after the impression; the inner join produces events for the case where feedback arrives before or at the same time as the impression. These events are marked explicitly as contributors to statistics. They will additionally be marked as done in the successful join case.<\/li>\n\n\n\n<li><em>Don\u2019t count an impression more than once in statistics:<\/em>\u00a0We add an explicit signal in all the events we produce based on a comparison of the event\u2019s input and output sync times. Only events whose input and output sync times are the same may contribute to statistics.<\/li>\n\n\n\n<li><em>Feedback can arrive up to 10 minutes after the associated impression:<\/em>\u00a0The lifetime of the impression stream is 10 minutes longer than the lifetime of the feedback stream.<\/li>\n\n\n\n<li><em>Delivery of the input logs can be delayed by up to 30 minutes on either side:\u00a0<\/em>The base lifetime of both the impression and feedback streams is 30 minutes.<\/li>\n<\/ul>\n\n\n\n<p>Although the problem seemed complicated at first, breaking it down into sub-problems and solving each of them individually made the entire process much more manageable. This is a skill that can take some time to master, but once you have a good understanding of what temporal operators are available and how they behave, the streamable APIs allow for near limitless expressiveness and power.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"next-time\">Next time<\/h2>\n\n\n\n<p>In our next Trill tutorial, we\u2019ll cover a different impression feedback mechanism that the Bing Ads processing pipeline handles: visibility. Visibility feedback is distinct from impression feedback in that it is based on user activity and does not arrive all at once.\u00a0 Until then!<\/p>\n\n\n\n<p>Feedback or questions? Let me know in the comments below.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is part 2 of 2-post series that shows you how to use Trill, an open source .NET library designed to process one trillion events a day, for impression feedback.<\/p>\n","protected":false},"author":5562,"featured_media":95480,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"msxcm_post_with_no_image":false,"ep_exclude_from_search":false,"_classifai_error":"","_classifai_text_to_speech_error":"","footnotes":""},"post_tag":[2272],"content-type":[340],"topic":[2238],"programming-languages":[],"coauthors":[621],"class_list":["post-77367","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","tag-microsoft","content-type-tutorials-and-demos","topic-ai-machine-learning","review-flag-1593580428-734","review-flag-1593580415-931","review-flag-1593580419-521","review-flag-1-1593580432-963","review-flag-2-1593580437-411","review-flag-3-1593580442-169","review-flag-alway-1593580310-39","review-flag-exclu-1593580297-613","review-flag-never-1593580314-283","review-flag-new-1593580248-669"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>How to use Trill for impression feedback (part 2) | Microsoft Open Source Blog<\/title>\n<meta name=\"description\" content=\"Part 2 of 2-post series that shows you how to use Trill, an open source .NET library designed to process one trillion events a day, for impression feedback.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to use Trill for impression feedback (part 2) | Microsoft Open Source Blog\" \/>\n<meta property=\"og:description\" content=\"Part 2 of 2-post series that shows you how to use Trill, an open source .NET library designed to process one trillion events a day, for impression feedback.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/\" \/>\n<meta property=\"og:site_name\" content=\"Microsoft Open Source Blog\" \/>\n<meta property=\"article:published_time\" content=\"2019-06-10T16:00:29+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-27T13:44:06+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2024\/06\/MSC21_homeGaming_Surface_002.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1170\" \/>\n\t<meta property=\"og:image:height\" content=\"640\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Zhong Chen\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@OpenAtMicrosoft\" \/>\n<meta name=\"twitter:site\" content=\"@OpenAtMicrosoft\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Zhong Chen\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"8 min read\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2019\\\/06\\\/10\\\/how-to-use-trill-impression-feedback-part-2\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2019\\\/06\\\/10\\\/how-to-use-trill-impression-feedback-part-2\\\/\"},\"author\":[{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/author\\\/zhong-chen\\\/\",\"@type\":\"Person\",\"@name\":\"Zhong Chen\"}],\"headline\":\"How to use Trill for impression feedback (part 2)\",\"datePublished\":\"2019-06-10T16:00:29+00:00\",\"dateModified\":\"2025-06-27T13:44:06+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2019\\\/06\\\/10\\\/how-to-use-trill-impression-feedback-part-2\\\/\"},\"wordCount\":1268,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2019\\\/06\\\/10\\\/how-to-use-trill-impression-feedback-part-2\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/wp-content\\\/uploads\\\/2024\\\/06\\\/MSC21_homeGaming_Surface_002.webp\",\"keywords\":[\"Microsoft\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2019\\\/06\\\/10\\\/how-to-use-trill-impression-feedback-part-2\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2019\\\/06\\\/10\\\/how-to-use-trill-impression-feedback-part-2\\\/\",\"url\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2019\\\/06\\\/10\\\/how-to-use-trill-impression-feedback-part-2\\\/\",\"name\":\"How to use Trill for impression feedback (part 2) | Microsoft Open Source Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2019\\\/06\\\/10\\\/how-to-use-trill-impression-feedback-part-2\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2019\\\/06\\\/10\\\/how-to-use-trill-impression-feedback-part-2\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/wp-content\\\/uploads\\\/2024\\\/06\\\/MSC21_homeGaming_Surface_002.webp\",\"datePublished\":\"2019-06-10T16:00:29+00:00\",\"dateModified\":\"2025-06-27T13:44:06+00:00\",\"description\":\"Part 2 of 2-post series that shows you how to use Trill, an open source .NET library designed to process one trillion events a day, for impression feedback.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2019\\\/06\\\/10\\\/how-to-use-trill-impression-feedback-part-2\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2019\\\/06\\\/10\\\/how-to-use-trill-impression-feedback-part-2\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2019\\\/06\\\/10\\\/how-to-use-trill-impression-feedback-part-2\\\/#primaryimage\",\"url\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/wp-content\\\/uploads\\\/2024\\\/06\\\/MSC21_homeGaming_Surface_002.webp\",\"contentUrl\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/wp-content\\\/uploads\\\/2024\\\/06\\\/MSC21_homeGaming_Surface_002.webp\",\"width\":1170,\"height\":640},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2019\\\/06\\\/10\\\/how-to-use-trill-impression-feedback-part-2\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to use Trill for impression feedback (part 2)\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/\",\"name\":\"Microsoft Open Source Blog\",\"description\":\"Open dialogue about openness at Microsoft \u2013 open source, standards, interoperability\",\"publisher\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#organization\",\"name\":\"Microsoft Open Source Blog\",\"url\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/08\\\/Microsoft-Logo.png\",\"contentUrl\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/08\\\/Microsoft-Logo.png\",\"width\":259,\"height\":194,\"caption\":\"Microsoft Open Source Blog\"},\"image\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/x.com\\\/OpenAtMicrosoft\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"How to use Trill for impression feedback (part 2) | Microsoft Open Source Blog","description":"Part 2 of 2-post series that shows you how to use Trill, an open source .NET library designed to process one trillion events a day, for impression feedback.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/","og_locale":"en_US","og_type":"article","og_title":"How to use Trill for impression feedback (part 2) | Microsoft Open Source Blog","og_description":"Part 2 of 2-post series that shows you how to use Trill, an open source .NET library designed to process one trillion events a day, for impression feedback.","og_url":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/","og_site_name":"Microsoft Open Source Blog","article_published_time":"2019-06-10T16:00:29+00:00","article_modified_time":"2025-06-27T13:44:06+00:00","og_image":[{"width":1170,"height":640,"url":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2024\/06\/MSC21_homeGaming_Surface_002.png","type":"image\/png"}],"author":"Zhong Chen","twitter_card":"summary_large_image","twitter_creator":"@OpenAtMicrosoft","twitter_site":"@OpenAtMicrosoft","twitter_misc":{"Written by":"Zhong Chen","Est. reading time":"8 min read"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/#article","isPartOf":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/"},"author":[{"@id":"https:\/\/opensource.microsoft.com\/blog\/author\/zhong-chen\/","@type":"Person","@name":"Zhong Chen"}],"headline":"How to use Trill for impression feedback (part 2)","datePublished":"2019-06-10T16:00:29+00:00","dateModified":"2025-06-27T13:44:06+00:00","mainEntityOfPage":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/"},"wordCount":1268,"commentCount":0,"publisher":{"@id":"https:\/\/opensource.microsoft.com\/blog\/#organization"},"image":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/#primaryimage"},"thumbnailUrl":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2024\/06\/MSC21_homeGaming_Surface_002.webp","keywords":["Microsoft"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/","url":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/","name":"How to use Trill for impression feedback (part 2) | Microsoft Open Source Blog","isPartOf":{"@id":"https:\/\/opensource.microsoft.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/#primaryimage"},"image":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/#primaryimage"},"thumbnailUrl":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2024\/06\/MSC21_homeGaming_Surface_002.webp","datePublished":"2019-06-10T16:00:29+00:00","dateModified":"2025-06-27T13:44:06+00:00","description":"Part 2 of 2-post series that shows you how to use Trill, an open source .NET library designed to process one trillion events a day, for impression feedback.","breadcrumb":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/#primaryimage","url":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2024\/06\/MSC21_homeGaming_Surface_002.webp","contentUrl":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2024\/06\/MSC21_homeGaming_Surface_002.webp","width":1170,"height":640},{"@type":"BreadcrumbList","@id":"https:\/\/opensource.microsoft.com\/blog\/2019\/06\/10\/how-to-use-trill-impression-feedback-part-2\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/opensource.microsoft.com\/blog\/"},{"@type":"ListItem","position":2,"name":"How to use Trill for impression feedback (part 2)"}]},{"@type":"WebSite","@id":"https:\/\/opensource.microsoft.com\/blog\/#website","url":"https:\/\/opensource.microsoft.com\/blog\/","name":"Microsoft Open Source Blog","description":"Open dialogue about openness at Microsoft \u2013 open source, standards, interoperability","publisher":{"@id":"https:\/\/opensource.microsoft.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/opensource.microsoft.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/opensource.microsoft.com\/blog\/#organization","name":"Microsoft Open Source Blog","url":"https:\/\/opensource.microsoft.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/opensource.microsoft.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/08\/Microsoft-Logo.png","contentUrl":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/08\/Microsoft-Logo.png","width":259,"height":194,"caption":"Microsoft Open Source Blog"},"image":{"@id":"https:\/\/opensource.microsoft.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/OpenAtMicrosoft"]}]}},"msxcm_animated_featured_image":null,"bloginabox_display_generated_audio":false,"distributor_meta":false,"distributor_terms":false,"distributor_media":false,"distributor_original_site_name":"Microsoft Open Source Blog","distributor_original_site_url":"https:\/\/opensource.microsoft.com\/blog","push-errors":false,"_links":{"self":[{"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/posts\/77367","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/users\/5562"}],"replies":[{"embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/comments?post=77367"}],"version-history":[{"count":1,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/posts\/77367\/revisions"}],"predecessor-version":[{"id":97747,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/posts\/77367\/revisions\/97747"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/media\/95480"}],"wp:attachment":[{"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/media?parent=77367"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/post_tag?post=77367"},{"taxonomy":"content-type","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/content-type?post=77367"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/topic?post=77367"},{"taxonomy":"programming-languages","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/programming-languages?post=77367"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/coauthors?post=77367"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}