<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Dmitry Trager on Medium]]></title>
        <description><![CDATA[Stories by Dmitry Trager on Medium]]></description>
        <link>https://medium.com/@dmitry.trager?source=rss-c8a01aca7492------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*wCPI1KUFOVRv2BM0.</url>
            <title>Stories by Dmitry Trager on Medium</title>
            <link>https://medium.com/@dmitry.trager?source=rss-c8a01aca7492------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 18:59:59 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@dmitry.trager/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Reading my Telegram]]></title>
            <link>https://medium.com/@dmitry.trager/reading-my-telegram-6e02371bda1?source=rss-c8a01aca7492------2</link>
            <guid isPermaLink="false">https://medium.com/p/6e02371bda1</guid>
            <dc:creator><![CDATA[Dmitry Trager]]></dc:creator>
            <pubDate>Sat, 12 Nov 2022 10:09:12 GMT</pubDate>
            <atom:updated>2022-11-12T10:09:12.750Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/888/1*YNkyiG0uyAMICeMtWPGOcQ.jpeg" /></figure><p>In my opinion, the top 3 things that Russian IT specialists gave this world are:</p><ul><li><a href="https://en.wikipedia.org/wiki/Tetris">Tetris</a></li><li><a href="https://en.wikipedia.org/wiki/Nginx">Nginx</a></li><li><a href="https://telegram.org/">Telegram</a></li></ul><p>Telegram is great, it has already become the main source of news for many people these days. Especially, its channels which are absent in Whatsapp or Viber by the way. But the constant reading of many channels through the day could be tiresome. What if someone reads the news for you? Or something instead of someone. I just have <a href="https://yandex.ru/alice/station-lite">Yandex Station Light</a> with <a href="https://yandex.ru/alice">voice helper Alice</a> inside. It is like <a href="https://en.wikipedia.org/wiki/Amazon_Alexa">Alexa</a> or <a href="https://en.wikipedia.org/wiki/Cortana_(virtual_assistant)">Cortana</a> in a box. So what if!</p><p>Alice allows writing user programs that can be reviewed, published, and then activated by voice interface. <a href="https://console.cloud.yandex.ru/">Yandex</a> calls these programs “skills” or “<a href="https://dialogs.yandex.ru/store">dialogs</a>”. So the goal here is to dive into writing new dialog, figure out how to work with <a href="https://core.telegram.org/">Telegram API</a>, and then connect these two things together.</p><h3>Dialogs for Alice</h3><p>Yandex has quite comprehensive <a href="https://yandex.ru/dev/dialogs/alice/doc/index.html">docs for dialogs</a>. Creating dialog is a simple process that you can do even from a console. The idea behind the dialog is that the station receives some phrase from the user, packs it into a request, and sends it to the dialogs backend. When the response is received, station speaks back to the user. When the next phrase is received, the state may be changed already, so this is the way for the dialog to evolve.</p><p>The main things you have to consider for new dialog:</p><ol><li>Unique name</li><li>Activation phrases: how to start working with dialog</li><li>Intents: different ways people will interact with the dialog</li><li>Hosting: cloud function or webhook URL</li><li>Access type: public or private</li></ol><p>While 1, 2, and 5 seem to be clear, lets get closer to 3 and 4.</p><h4>Intent</h4><p>When you design the way communication is going to happen you have some scenarios in mind. At every step of any scenario Alice should know what exactly you want from her. This means that your phrase should contain meaningful information in terms of your specific dialog. Think of it like communication protocol you should follow. <a href="https://yandex.ru/dev/dialogs/alice/doc/nlu.html">The intent</a> is the way to extract this information related to some protocol. For example, “what is the weather for tomorrow in Helsinki” and “order me a taxi for 10 pm” are two phrases with different intents.</p><p>Yandex uses <a href="https://en.wikipedia.org/wiki/Neuro-linguistic_programming">NLP</a> (Natural Language Processing) to do the main job, but we need to help it in parsing phrases. Even though the difference between the two intents above looks quite obvious, we need to describe valuable pieces of information there. So the first dialog will start looking like calling function <em>weather</em> with arguments <em>when</em>=”tomorrow” and <em>where</em>=”Helsinki”. The second one will look like calling function <em>orderTaxi</em> with argument <em>where</em>=&lt;my_current_location&gt; (default value) and <em>when</em>=”10 pm”.</p><p>Intents and their grammar can be described via the web interface. This process is not complicated: there are only a few terms you need to get familiar with. But please keep in mind that there are only 4 types for named entities right now: names, locations, dates, and numbers. Yandex <a href="https://yandex.ru/dev/dialogs/alice/doc/naming-entities.html">provides</a> type description for them and can effectively recognise these type of values from your voice input. When your intents are fully described, all you need to do is to describe corresponding structures within your backend and start working with data coming from dialog.</p><h4>Hosting</h4><p>Yandex Cloud Functions (or just YCF) is a technology from <a href="https://en.wikipedia.org/wiki/Serverless_computing">a serverless</a> family. It is one of the options to host the backend for your dialog. Processing time for the function that serves Yandex dialog is not charged by the company. This is the good part. The bad part is that “serverless” type of processing has some limitations. I will get back to these limitations later.</p><p>Another option is using a webhook URL, which means that you can host your dialog on any public server, grab the URL of your deployed backend, and put it into dialog settings. It is quite universal but requires more effort to deploy your code. This is the option I had to choose for now.</p><h3>Telegram API</h3><p>I had decided to try using <a href="https://go.dev/">Go</a> for working with Telegram (or just Tg). I wanted to practice this language and it was the best option for me in the list of languages compatible with YCF. I was lucky to find <a href="https://github.com/xelaj/mtproto">a full-native go implementation of Tg API</a>. Unfortunately, the last version (1.0.0) was released almost a year ago. But I was able to find all the functionality that I needed in this package.</p><p>When you want to work with Tg API, you have to create a <a href="https://core.telegram.org/api/obtaining_api_id">new application</a> first. You will get the application id, application secret hash, and server host address. All these parameters are required to initialise the Tg client. Along with this initialisation Tg client also inits the storage for session data and public keys. This storage should be local and this is where the problem comes from.</p><p>YCF does not allow you to store any data locally because of its serverless nature. This means you can not use this Tg client and YCF in pair. This is the reason I had to use local/public server to host backend for dialog that uses Tg API and switch my dialog to using webhook URL.</p><p>Except procedures of logging in and confirming phone number with code I need to work with some other Tg entities to make the whole logic work. The most essential of these entities are:</p><ul><li><a href="https://core.telegram.org/constructor/messages.chats">Chat</a><br>I am mostly interested in channels, but channel is the type of chat in Tg world. In order to get chats I use <a href="https://core.telegram.org/method/messages.getAllChats"><em>getAllChats</em></a> endpoint.</li><li><a href="https://core.telegram.org/constructor/messages.messages">Message</a><br>Every chat contains messages. A list of messages is available through history, which can be fetched using <a href="https://core.telegram.org/method/messages.getHistory"><em>getHistory</em></a> endpoint.</li></ul><p>So my backend is just some processing around Tg chats and messages: find chat, get number of messages from this chat etc.</p><h3>Other obstacles</h3><ul><li>Tg API has the option <em>InitWarnChannel</em> for client config.<br>Switch this one to “false” and you won’t see different client warnings in the process output</li><li>When a connection with Tg server is created the first time, the client starts “keepalive” pinging and reading responses from the server. When the received message is not “nil”, “context.Canceled”, or “io.EOF” we get panic which is not I expect to happen.<br>This is a bug that was not fixed at the moment and I don’t know if it is fixed now. Replace checking error with something else within <em>startReadingResponses</em> function to avoid this behaviour.</li></ul><h3>Some results</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fplayer.vimeo.com%2Fvideo%2F765205073%3Fh%3Daf54fcfc6e%26app_id%3D122963&amp;dntp=1&amp;display_name=Vimeo&amp;url=https%3A%2F%2Fvimeo.com%2F765205073&amp;image=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F1536488600-21cac7a40c5f9eb8e4a42601dc177c0039f89b667baa7626b63a502239936ee7-d_960&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=vimeo" width="1080" height="1920" frameborder="0" scrolling="no"><a href="https://medium.com/media/e406f0d67b23dae99d4c32a3ab151555/href">https://medium.com/media/e406f0d67b23dae99d4c32a3ab151555/href</a></iframe><p>At least, this thing works. It was deployed to <a href="https://lightsail.aws.amazon.com/">Amazon Lightsail</a> server as a Golang API in a <a href="https://www.docker.com/">Docker</a> container using some <a href="https://www.ansible.com/">Ansible</a> scripts. By the way, I found it quite useful to manage some of my pet project with one common Ansible repository.</p><p>After all I have managed this to work along with self-hosted backend. Managing several Tg sessions remains tricky part for me: since I only have one Yandex/Alice account, I can easily test with one session only. Of course, public dialog has to manage many sessions. Thus, publishing of this dialog does not seem to happen soon. Maybe I should leave it private and be a sole user here. Haha!</p><p>At the same time some services can give you a lot of data without account specifying. When “News and Trends” topic arises the very first thing that comes to my mind is <a href="https://twitter.com/">Twitter</a>. I can realise that dozens of tweets contain media and so we need rather to see content than to read it. But text is still important for many people who would like to receive news. So againg: what if!</p><h3>Plans</h3><p>At a first glance this new dialog should provide these main options:</p><ul><li>read home timeline (most recent Tweets, Retweets, and replies posted by the authenticated user and the accounts they follow)</li><li>read specific user timeline (published by a specific Twitter account)</li><li>search by tag, keyword or whatever</li></ul><p>If you have any ideas with respect to these plans, please let me know in replies. Thank you for reading.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6e02371bda1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Third party mocks]]></title>
            <link>https://medium.com/@dmitry.trager/third-party-mocks-5013aa9ff587?source=rss-c8a01aca7492------2</link>
            <guid isPermaLink="false">https://medium.com/p/5013aa9ff587</guid>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[mock-server]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Dmitry Trager]]></dc:creator>
            <pubDate>Mon, 19 Oct 2020 19:22:14 GMT</pubDate>
            <atom:updated>2020-10-19T19:22:14.757Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/661/1*Kcal93Gf9JWQQibG7WvhqQ.jpeg" /><figcaption>Pic is slightly related (from https://habr.com)</figcaption></figure><p>Hello. In this post I would like to cover one more topic related to my previous <a href="https://medium.com/@dmitry.trager/do-not-even-try-yourself-20008fb0e90a">post</a> theme. It is all about feature tests for web applications with reach frontend.</p><p>Sometimes we have to deal with requests to third-party services. If request goes from backend we can stub it, we can use VCR or whatever. But things differ when this request goes from frontend. We assume, that our backend is written on Rails. Request fired on frontend side does not go though Ruby part of application. But we do not want to have real requests in test environment. What to do then?</p><p>We need to emulate target server for requests coming from frontend. Literally, this means that we need to provide second test server in a way. Luckily, this can be done with the following snippet of code:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0d1b2d8a781c35a59e241df5b14c188e/href">https://medium.com/media/0d1b2d8a781c35a59e241df5b14c188e/href</a></iframe><p>Here we describe Sinatra application with just one POST route. Don’t forget to provide some headers in order to bypass CORS protection. Additional gem is also used for this purpose. Finally, ‘discoball’ gem is used here to spin separate test server. But you can do this with simple threads if you would like to.</p><p>One more thing you have to do is to replace your 3rd-party server with our new Sinatra server for frontend. This was the reason to save second server url in application config. So now you can write in you html layout (slim template in my case):</p><blockquote>javascript:<br> DEMO_API_URL = “#{Rails.configuration.x.demo_app.api_url}”</blockquote><p>and then in javascript simply use:</p><blockquote>window.DEMO_API_URL</blockquote><p>That’s it. Happy testing!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5013aa9ff587" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Do not even try… yourself]]></title>
            <link>https://medium.com/@dmitry.trager/do-not-even-try-yourself-20008fb0e90a?source=rss-c8a01aca7492------2</link>
            <guid isPermaLink="false">https://medium.com/p/20008fb0e90a</guid>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[capybara]]></category>
            <category><![CDATA[rspec]]></category>
            <category><![CDATA[test]]></category>
            <category><![CDATA[features]]></category>
            <dc:creator><![CDATA[Dmitry Trager]]></dc:creator>
            <pubDate>Mon, 28 Sep 2020 07:27:07 GMT</pubDate>
            <atom:updated>2020-09-28T07:27:07.030Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/640/1*bOSD1qrlaFOz0A2z8tac1A.jpeg" /><figcaption>Just a random successful failure joke</figcaption></figure><p>If you have web application with reach frontend, you are better to have some feature tests, right? In Ruby world we use to have Capybara/Rspec and some JS driver for these purposes. But it often happens that your frontend logic is complicated enough to have random failures in tests. And you see messages, for example, like this:</p><blockquote>Failure/Error: expect(page).to have_selector(‘[data-testid=homepage]’) expected to find css “[data-testid=homepage]” but there were no matches</blockquote><p>or this:</p><blockquote>Capybara::Ambiguous: Ambiguous match, found 2 elements matching visible css “[data-testid=button]” with text “Create group”</blockquote><p>or better this:</p><blockquote>Failure/Error: expect(page).to have_content(‘Captcha Verification failed. Please try to sign up again.’)</blockquote><p>Captcha verification can be failed successfully on next run. You just need to rerun your tests. But time after time this becomes boring. Yes, it is better to get rid of this cases in your tests, but if you just need to save some time at current stage, here is the tool: <a href="https://github.com/NoRedInk/rspec-retry">rspec-retry</a>. Simple setup in your test helper may look like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3123f19cfa070d5e270ea2b44b285b07/href">https://medium.com/media/3123f19cfa070d5e270ea2b44b285b07/href</a></iframe><p>This setup will rerun your failed feature js tests automatically. Env variable is used here in order not to retry tests on your local machine unless you really want it. So that’s it: CI is going to rerun your tests instead of you from that moment. And of course, once you are done with random failures, this little snippet can be easily removed. Happy testing!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=20008fb0e90a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Working with product team: inside view]]></title>
            <link>https://medium.com/flatstack-blog/working-with-product-team-inside-view-9d37cf8877ad?source=rss-c8a01aca7492------2</link>
            <guid isPermaLink="false">https://medium.com/p/9d37cf8877ad</guid>
            <category><![CDATA[bugs]]></category>
            <category><![CDATA[qa]]></category>
            <category><![CDATA[flatstack]]></category>
            <category><![CDATA[prioritization]]></category>
            <category><![CDATA[agile]]></category>
            <dc:creator><![CDATA[Dmitry Trager]]></dc:creator>
            <pubDate>Fri, 29 Mar 2019 16:14:31 GMT</pubDate>
            <atom:updated>2019-03-29T16:14:31.135Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*h3-qc1w0AiUOuyazwA-7uA.jpeg" /><figcaption>Image from: <a href="http://community.uservoice.com/blog/scaling-your-product-management-team/">http://community.uservoice.com</a></figcaption></figure><p>Lately I’ve been working with the product team. I mean the team working on its own product, not something like outsource and product owner or product manager on the other side. I was told before that this should be “completely different experience”. Well, I wouldn’t say so, for me it’s not like “completely” different. But many things differ in interesting way. And today I wanted to share some of my thoughts.</p><p><strong>Planning and estimation.</strong></p><p>Really, team does not care too much about velocity. Task tracker (<a href="https://www.pivotaltracker.com/">Pivotal</a> in this specific case) is doing all the job itself. So it knows exactly what tasks can fit into sprint. This helps team to focus on important goals for current iteration and not interrupt it. As usual, nothing special.</p><p>The difference is that team <em>rarely have to spend time thinking about performance</em> decrease and its reasons — there are too many causes, it really makes unreasonable this spending of time. So the only thing team needs to do (when all the tasks are in place) is to think about priorities.</p><p><strong>Stories and epics.</strong></p><p>Fast growing product has to involve more and more new functionality. Which means big features. Which, in its turn, means that you are going to face epics quite often. Just to confirm: epic here means huge story (can not be estimated using given limit of story points) that you can split into several small stories (can be estimated using given limit of story points) but can not deliver until every story is fully finished.</p><p>One important thing related to epics is accepting it in terms of <a href="https://en.wikipedia.org/wiki/Quality_assurance">QA</a> (quality assurance). Let’s suppose you have staging environment in your applications (several apps within one big project, for example), QA specialist and somebody who sets a task (just like product owner) are two different persons, epic story includes tasks for design, for different applications etc. Well, things get complicated. Looks like you need to develop a <em>special flow for accepting tasks within epic</em> and the epic itself. Maybe some items in the list above are not so relevant for you, but accepting may become a problem if you don’t keep things in mind.</p><p><strong>Support and bugs.</strong></p><p>This is quite obvious, but still: if you want to have loyal customers you need both QA and customer support. In every live product bugs are coming constantly into backlog and, on the other hand, you are trying to check features before deployment. But when you are in product team, you <em>have to prioritise bugs</em>.</p><p>This becomes your and your peers’ responsibility, not someone’s else. So you can face the situation when something needs to be fixed “right now” (or even “yesterday”) and other thing may not be fixed at all (I mean ever, really). Just because at any given time you understand that there is something more important. Sound weird on the first glance, but so it is.</p><p><em>Keep hacking and stay true!</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9d37cf8877ad" width="1" height="1" alt=""><hr><p><a href="https://medium.com/flatstack-blog/working-with-product-team-inside-view-9d37cf8877ad">Working with product team: inside view</a> was originally published in <a href="https://medium.com/flatstack-blog">Flatstack Thoughts</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Rsyslog and log management]]></title>
            <link>https://medium.com/flatstack-blog/rsyslog-and-log-management-8be3e4780406?source=rss-c8a01aca7492------2</link>
            <guid isPermaLink="false">https://medium.com/p/8be3e4780406</guid>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[flatstack]]></category>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[logging]]></category>
            <category><![CDATA[rsyslog]]></category>
            <dc:creator><![CDATA[Dmitry Trager]]></dc:creator>
            <pubDate>Mon, 18 Feb 2019 16:50:01 GMT</pubDate>
            <atom:updated>2019-02-18T16:50:01.677Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/750/0*zj1RrQ88dT7yF7i4.jpg" /><figcaption>Image from <a href="https://unrealitymag.com/">https://unrealitymag.com</a></figcaption></figure><p>When you do any type of software, logs are very important part of ecosystem. They help to understand what happens at any given time. They are quite irreplaceable when you need to find root cause of a bug. This works for web applications as well. But when you have one app with hundreds and thousands of users, you have huge amount of logs. And it is a good practice to manage it somehow.</p><p>There are many services for log management nowadays. And they allow to use different types of integration with your application. Usually, the most convenient way for developer is language specific library. Because who wants to use API directly, right?)</p><p>Let’s say we are working on Rails application. We have number of solutions for Ruby integration. But they often have some inconveniences. For example, <a href="https://logentries.com/">Logentries,</a> being a very popular service, offers <a href="https://github.com/rapid7/le_ruby">Ruby gem,</a> which just replaces standard logger. This means that with this integration all the logs will go to the service and you won’t have text backups on your server. This sounds inconvinient for me, almost inappropriate. Though I have to say that for Heroku hosting option I would consider it quite useful.</p><p>Another example is <a href="https://coralogix.com/">Coralogix</a> that I’ve tried recently. They suggest different approach. There is a <a href="https://github.com/rails/rails/blob/5-2-stable/activesupport/lib/active_support/logger.rb#L24">feature</a> in <a href="https://github.com/rails/rails/tree/master/activesupport">ActiveSupport</a> that allows message broadcasting to many loggers. So your custom logger can just be added to the system in order to start receiving messages. The problem is that these messages will have pure text. For example, if you you are using tagged logging in Rails app, tags are not going to be included in text that you send to Coralogix. So you’ll have two version of logs: rich text on your server and plain text on Coralogix server. A little bit less inconvenient for me, but still.</p><p>Ok, so how do we do integration at the end? When you have several different services working on different platforms solution comes by itself. Usually, we have our servers running on Linux-based machines, most of them have <a href="https://www.rsyslog.com/">rsyslog</a> (or syslog-ng) service included. Here is the quote from official site:</p><blockquote>It offers high-performance, great security features and a modular design. While it started as a regular syslogd, rsyslog has evolved into a kind of swiss army knife of logging, being able to accept inputs from a wide variety of sources, transform them, and output to the results to diverse destinations.</blockquote><blockquote>RSYSLOG can deliver over one million messages per second to local destinations when limited processing is applied. Even with remote destinations and more elaborate processing the performance is usually considered “stunning”.</blockquote><p>What you need to do with this approach is to setup config for rsyslog. It includes several options, but the most important parts are:</p><ol><li><em>template</em> that describes message format (goes within “template” section)</li><li>number of <em>filters</em> that allows you, for example, to pick only one service’ messages from syslog (goes within “filters” section)</li><li><em>destination</em> that describes where your templates are going to be sent to (goes within “destination” section)</li></ol><p>First and third are usually specific to the service you use when second usually describes your personal needs to filter data. There is configuration section as well, but default settings are often fine there.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a65d1be125a3cec6aeea415c15bc29c1/href">https://medium.com/media/a65d1be125a3cec6aeea415c15bc29c1/href</a></iframe><p>I have to say that when you need smart filtering, for example, config setup may become very tricky. But in many cases you just create new simple config, put it with other configs, restart rsyslog service. And that’s it! You may want to include this into some deployment configuration that you have for your servers (ansible role, for example). But general steps are very simple.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8be3e4780406" width="1" height="1" alt=""><hr><p><a href="https://medium.com/flatstack-blog/rsyslog-and-log-management-8be3e4780406">Rsyslog and log management</a> was originally published in <a href="https://medium.com/flatstack-blog">Flatstack Thoughts</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What did we get from Semaphore 2.0]]></title>
            <link>https://medium.com/flatstack-blog/what-did-we-get-from-semaphore-2-0-b85c4f9a15d7?source=rss-c8a01aca7492------2</link>
            <guid isPermaLink="false">https://medium.com/p/b85c4f9a15d7</guid>
            <category><![CDATA[jobs]]></category>
            <category><![CDATA[semaphore]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[backend]]></category>
            <category><![CDATA[flatstack]]></category>
            <dc:creator><![CDATA[Dmitry Trager]]></dc:creator>
            <pubDate>Tue, 08 Jan 2019 11:08:51 GMT</pubDate>
            <atom:updated>2019-01-08T11:08:51.684Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*keEM6rH9darf3VVsQm6-tw.png" /><figcaption>Image from <a href="https://semaphoreci.com/">https://semaphoreci.com</a></figcaption></figure><p><a href="http://semaphoreci.com/">Semaphore</a> 2.0 was <a href="https://semaphoreci.com/blog/2018/11/06/semaphore-2-0-launched.html">officially released</a> recently. It brings “customizable CI/CD pipelines, autoscaling and more” to us. Let’s try to figure out, how can we switch to brand new Semaphore and do we need it at all.</p><p><strong><em>Note</em></strong><em>: this is a quick view, many new </em><a href="https://docs.semaphoreci.com/article/53-sem-reference"><em>sem</em></a><em> commands, dashboard setup and other things were not covered at all.</em></p><p>According to official guides, we need first of all to get new CLI tool called “<a href="https://docs.semaphoreci.com/article/53-sem-reference">sem</a>”. It will help to connect to Semaphore endpoint and initialise new project. New project means <em>.semaphore/semaphore.yml</em> file only because this is all you need to start. If we open new Semaphore’s home page in browser, there are “Dashboards” and “Projects” sections in the sidebar. Not sure if I need any dashboards so let’s go to projects. We can see pushed branches here and commit statuses. How do we get tests to pass?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mFQ4rvtcJWdglo93JXOwJA.png" /><figcaption>New Semaphore interface</figcaption></figure><p>Well, instead of project setup in browser you can describe everything with YAML configuration now. It is supposed for you to have blocks of tasks that have jobs inside. Blocks/tasks go one by one and jobs (within tasks) can run in parallel. In general, you need to make jobs based on your previous Semaphore project settings. Let’s try.</p><p><em>“In fact, this is the simplest version of Semaphore ever.” — Semaphore team.</em></p><p>When you start new project configuring, you’ll quickly figure out, that everything that were in settings before now needs to be described explicitly: not just you have to set Ruby version or DB type, you need to start DB server, do checkout, you even need to install <a href="https://gist.github.com/galulex/2304847"><em>imagemagick</em></a> and some other packages yourself (in case if it’s required by setup, of course). Env vars can not be encrypted anymore, but more important, that you have to set the same vars for every block where you use them — they just can not be moved to upper block. This goes to duplication that is especially disturbing when you have to have many env vars.</p><p>Being able to configure everything with YAML file should bring a wide range of possibilities. At least, in theory. In practice, I expect CI to duplicate my regular testing flow. And here is the question: do you stop DB server between test run? Maybe you constantly switch Ruby versions? I don’t. So including these “obvious” steps seems weird to me.</p><p>Being user of this service (I mean Semaphore here) and having some projects working already, I would like to have some tool for config generation. Or maybe some magic button to dump config from existing setup. But this is not what we have. In this case <a href="https://docs.semaphoreci.com/article/88-migration-guide-for-semaphore-classic-users">migration guide</a> should be really comprehensive. But this is not what we have either.</p><p>It was quite easy for me to describe a block for application setup. Just several iterations. But next I had to run tests and code quality check. I got stuck with the fact that gems were not found in next block. Debugging a job with ssh did not help since my folder was not there. I still don’t know why it happens. But I know, that Semaphore creates it’s own <em>.env</em> file there and my setup with <a href="https://github.com/bkeepers/dotenv">dotenv</a> doesn’t work anymore (script does not copy file if it is already in place). As for the gems, support guy suggested to include “<em>bundle install”</em> string as one of block commands. Well, good point of course. But why do I need <a href="https://github.com/markoa/demo-rails5/blob/master/.semaphore/semaphore.yml">these tricks</a> with bundle cache then?</p><p>And why to I need to do db setup, redis start and other things if it was done in previous block? Probably, this is not how it works and I don’t need a “setup” block at all: everything can go into prologue step of main block. It was not supposed from the docs, but ok. Can I run my tests now? Not yet, let’s look into some brand new issues with <a href="https://github.com/rmagick/rmagick">rmagick</a> first. Sorry guys, but I had to stop at some point.</p><p>As a conclusion, here is what you can get with new Semaphore:</p><ol><li>You have “customizable CI/CD pipelines, autoscaling and more”</li><li>You have “flexibility” and wide range of options for setup</li><li>You are forced to duplicate config (env vars, prologue etc)</li><li>You are forced to use prologue if you don’t want to duplicate commands too</li><li>You can not encrypt env vars anymore (at least, easily)</li><li>You need to do things that you do not repeat in usual flow (start DB server, change ruby version etc)</li><li>You don’t have well-written migration guide</li><li>You don’t have config generator</li><li>You can not use dotenv as before</li><li>You have to fight with new issues. Who would doubt that? ;)</li></ol><p><em>Do not despair! Keep hacking!</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b85c4f9a15d7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/flatstack-blog/what-did-we-get-from-semaphore-2-0-b85c4f9a15d7">What did we get from Semaphore 2.0</a> was originally published in <a href="https://medium.com/flatstack-blog">Flatstack Thoughts</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Domain forwarding in Rails applications]]></title>
            <link>https://medium.com/flatstack-blog/domain-forwarding-in-rails-applications-755ac75a87f2?source=rss-c8a01aca7492------2</link>
            <guid isPermaLink="false">https://medium.com/p/755ac75a87f2</guid>
            <category><![CDATA[dns]]></category>
            <category><![CDATA[domains]]></category>
            <category><![CDATA[https]]></category>
            <category><![CDATA[flatstack]]></category>
            <category><![CDATA[godaddy]]></category>
            <dc:creator><![CDATA[Dmitry Trager]]></dc:creator>
            <pubDate>Fri, 16 Nov 2018 10:46:38 GMT</pubDate>
            <atom:updated>2018-11-16T10:46:38.148Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pCi9KYiKC9pdxNMiVEwi8w.jpeg" /><figcaption>Error page of Godaddy.com URL shortener service</figcaption></figure><p><strong>TLDR</strong> If you use GoDaddy for DNS management, do it with caution. If possible, do not use it at all.</p><blockquote>The number of sites switching from HTTP to HTTPS as the default connection protocol has grown drastically in the last few years. It’s sufficient to have a look at the <a href="https://letsencrypt.org/2016/06/22/https-progress-june-2016.html">Let’s Encrypt progress report for June 2016</a> to get a sense of how fast the web is moving to HTTPS.</blockquote><blockquote>The increasing adoption of HTTPS as the default connection protocol for websites has introduced a few new challenges to developers and system administrators. One of them is the need to properly handle redirects to the canonical address of the website to avoid the risk of content duplication. More importantly, configuring the appropriate redirect is necessary to offer a consistent and secure browsing experience.</blockquote><p>This is what <a href="https://blog.dnsimple.com/2016/08/https-redirects/">DNSimple blog</a> tells us. They say that we often need to support up to 4 main redirects if we want to forse ssl with canonical domain. And one common way to do this is domain forwarding.</p><p>Also, when we use <a href="https://devcenter.heroku.com/articles/custom-domains#configuring-dns-for-root-domains">Heroku</a> and need a custom domain, it <a href="https://devcenter.heroku.com/articles/custom-domains#configuring-dns-for-root-domains">tells us</a> to use DNS hosting with ALIAS/ANAME records support. If you don’t have any (like you are on <a href="https://godaddy.com/">GoDaddy</a>, for example), here is the basic <a href="https://github.com/IcaliaLabs/guides/wiki/Domain-configuration-using-Heroku-&amp;-GoDaddy">manual</a> which describes how to do similar thing using domain forwarding. The point is the following: is does not work correctly. It <a href="https://seortp.com/godaddy-forwarding/">looks like</a> it was fine before. But now they switched the way of forwarding domains via a URL Shortener. So it forwards main domain only. But if you forward path (and query params) it can bring you to the page shown on the image above.</p><p>Guys from support can say that this is how domain forwarding should really work. In fact, everything is perfectly described is <a href="https://ph.godaddy.com/community/Managing-Domains/Domain-forwarding-path-being-lost/td-p/111686">this post</a>, where it’s confirmed that service was changed to “increase stability and performance as well as address other internal issues”. And that’s why “the path match no longer works for any forwarding”. So please, be careful.</p><p>In our case we had to implement some workaround solution and add subdomain to “long” links so it does not require redirection. But this is a temporary solution and general plan is to choose new DNS hosting with ALIAS records support.</p><p><em>Happy hacking!</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=755ac75a87f2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/flatstack-blog/domain-forwarding-in-rails-applications-755ac75a87f2">Domain forwarding in Rails applications</a> was originally published in <a href="https://medium.com/flatstack-blog">Flatstack Thoughts</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[instance_exec to the rescue? not so fast!]]></title>
            <link>https://medium.com/flatstack-blog/instance-exec-to-the-rescue-not-so-fast-3388bb27eeed?source=rss-c8a01aca7492------2</link>
            <guid isPermaLink="false">https://medium.com/p/3388bb27eeed</guid>
            <category><![CDATA[debug]]></category>
            <category><![CDATA[flatstack]]></category>
            <category><![CDATA[code]]></category>
            <category><![CDATA[gem]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Dmitry Trager]]></dc:creator>
            <pubDate>Thu, 11 Oct 2018 16:08:10 GMT</pubDate>
            <atom:updated>2018-10-11T16:08:10.132Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/630/1*iaaFoZAOvYet4tbdxaF8bw.jpeg" /><figcaption>Print from <a href="https://www.teepublic.com/">https://www.teepublic.com/</a></figcaption></figure><p>Once I was searching for a bug in code and found this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/df062d7d612700c5638f7196d3e98bb4/href">https://medium.com/media/df062d7d612700c5638f7196d3e98bb4/href</a></iframe><p>The problem with this code is in the line 12: when you use find_or_initialize_by method block is called only for initialized instance. So existing timelapse instance is not passed to build_timelapse and not updated in our case.</p><p>Ok, let’s move out this block from find_or_initialize_by call. We should have something like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/601/1*PipYU9Icg_AqibWR5T6D6g.png" /><figcaption>Making the code a little bit dirty</figcaption></figure><p>The most obvious way is to move build_timelapse method into <em>Timelapse</em> model — in this case we’d just call t.build_timelapse or t.build_from_schedule i.e. renamed version. But here I started to torture myself: we need to do this only for scheduled timelapses, so why do we add this method to every instance? Let’s find another way even if it is going to be tricky![1]</p><p>Ok, if we find a way to call this method in context of timelapse we won’t even need this nasty local variable. Just this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/601/1*Z7Ya-CLCONauiymz4Yz7Zw.png" /><figcaption>Can we do it cleaner?</figcaption></figure><p>But how do we do this? The very famous way is instance_eval, but this is not exactly what we need here since we are going to set values in our method (probably replacing timelapse with self) which means passing parameters and instance_eval is not suitable for this task [2]. instance_exec to the rescue?</p><p>Not so fast! <a href="https://apidock.com/ruby/Object/instance_exec">Documentation</a> says that this method executes given block within the context of the receiver and self is set to receiver object. And it handles arguments for the block too. Great, but we still need to pass a block somehow. Yes, we can turn method into block of code: something like &amp;method(:build_timelapse).to_proc. But this won’t work. In this case method will not start behave like proc. This method is bounded to instance of <em>ScheduleTimelapse</em>, not <em>Timelapse</em>, and it won’t accept call to self properly. Even if we do unbound it can only be bounded back to the instance of <em>ScheduleTimelapse</em> class.</p><p>This was my main upset — there was no way to do exactly what I’d planned to do and I had to convert this method to lambda accepting one argument schedule. Finally, I was able to rewrite the code this way:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6d18347cf717b09915cbd8c0931d8307/href">https://medium.com/media/6d18347cf717b09915cbd8c0931d8307/href</a></iframe><p><em>[1] We can do </em><em>build_timelapse(timelapse, schedule) and just make this method return </em><em>timelapse as a result. This should work, but I don’t like this way since it is going to prevent method from being kind of “pure function”.</em></p><p><em>[2]</em> <em>There is a note about arguments in the </em><a href="https://apidock.com/ruby/v1_8_7_330/Object/instance_exec"><em>docs,</em></a><em> but difference is not indicated explicitly. You can find additional info in </em><a href="https://www.saturnflyer.com/blog/the-difference-between-instanceeval-and-instanceexec"><em>article</em></a><em> about FactoryGirl gem (which is FactoryBot now).</em></p><p>Happy hacking! Thanks.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3388bb27eeed" width="1" height="1" alt=""><hr><p><a href="https://medium.com/flatstack-blog/instance-exec-to-the-rescue-not-so-fast-3388bb27eeed">instance_exec to the rescue? not so fast!</a> was originally published in <a href="https://medium.com/flatstack-blog">Flatstack Thoughts</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elixir. Some performance remarks]]></title>
            <link>https://medium.com/flatstack-blog/elixir-some-performance-remarks-4b5bd10202b6?source=rss-c8a01aca7492------2</link>
            <guid isPermaLink="false">https://medium.com/p/4b5bd10202b6</guid>
            <category><![CDATA[gigalixir]]></category>
            <category><![CDATA[flatstack]]></category>
            <category><![CDATA[elixir]]></category>
            <category><![CDATA[heroku]]></category>
            <category><![CDATA[redis]]></category>
            <dc:creator><![CDATA[Dmitry Trager]]></dc:creator>
            <pubDate>Fri, 21 Sep 2018 10:50:48 GMT</pubDate>
            <atom:updated>2018-09-21T10:50:48.313Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/608/1*5hNhCXaJbuZGt-eBXqXycg.jpeg" /><figcaption>Image from <a href="https://bexinfu.com/2010/11/14/dropmind-a-drop-of-magic-in-your-elixir-of-creativity/">https://bexinfu.com/</a></figcaption></figure><p>Recently, we had a small task: API with couple of endpoints making authentication based on asymmetric encryption and JWT. Application was required to perform relatively big number of requests per second. That’s why we decided to try <a href="http://elixir-lang.org/">Elixir</a>. Here are some notes based on our attempts to create such an app and measure performance.</p><p>In our case execution cycle within endpoint includes: DB operations (quite simple, but still), Redis operations (save/read challenge message) and encryption/verification logic (verify encrypted challenge message, create JWT). So measuring relative performance was kind of goal. What did we learn so far?</p><ol><li>Making such a prototype is not a big deal. Having tools like <a href="https://hex.pm/packages/ex_crypto">ex_crypto</a> (to verify signed messages), <a href="https://hex.pm/packages/exredis">exredis</a> (to work with Redis), <a href="https://hex.pm/packages/rsa_ex">rsa_ex</a> (to generate keypairs) and, of course, <a href="https://hex.pm/packages/guardian">guardian</a> (swiss army knife for Elixir/Phoenix projects, in our case — JWT source) made the process very smooth.</li><li>Without OOP we can still use modules to encapsulate code responsibilities and provide some analogue of <a href="https://en.wikipedia.org/wiki/Single_responsibility_principle">SRP</a> in our application. Having pattern matching mechanic, pipelines, fallbacks controller mechanics and views separated from templates is not an obstacle but rather good facility to create json api following described protocol.</li><li>Generate a million of records on Heroku is not a big deal. But with keypairs generation this task becomes time consuming. I was unable to to do this directly from console because of Heroku timeouts. Having this task run with detached console, I was unable to get 1_000_000 records as well — it stopped after ~300_000 for some reasons.</li><li>You can easily create mix tasks and run it on Heroku. In my case I had two tasks: one to get public key stored in random record and one to use it in <a href="https://github.com/wg/wrk">wrk</a> benchmarking. Combining tasks, however, was not an easy goal. I spent some time struggling with redundant slashes in encoded string and had to give up (because of time limitations).</li><li>We can have 300–500 requests per second ever on free dyno. And free plan for Redis add-on (with 10 connections) seems like enough too. However, I’ve got plenty of responses with 500 code while benchmarking. I’m going to play with dyno types to check if it can help.</li><li>We used <a href="https://hex.pm/packages/benchee">benchee</a> for internal benchmarking. Just to compare execution time for different parts of our endpoints. Running benchmark from mix task, I got results showed on the image below. It just proved that for the first endpoint working with DB is the most time consuming operation. I’m going to try the same thing later to compare DB operations time with key verification operations time.</li><li>You can play with all these things not only on Heroku. Well, in theory. There is a new service called <a href="https://gigalixir.com/">Gigalixir</a> (you can watch the demo from <a href="https://elixircasts.io/deploying-with-gigalixir">this screencast</a>). It is supposed to behave very the same as Heroku, but for Elixir apps. It has account settings, manageable apps, PG tool etc — everything is managed though the cli tool (which is installed via <a href="https://pip.pypa.io/en/stable/">pip</a>). Finally, I was able to run application (response from support guys was very quick). Unfortunately, I was unable to run benchmarks. Service is quite raw at the moment. There is no web dashboard, no add-ons available from the console (we need Redis for this app) and I just couldn’t run seeds.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/708/1*njcXBe6_X1J34i6dk9Fegg.png" /><figcaption>Benchee results</figcaption></figure><p><em>[I suppose that I will be able to solve problems on Gigalixir with help of support team. But now there is no luck after couple of days spent.]</em></p><p>Thanks for reading and happy hacking!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4b5bd10202b6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/flatstack-blog/elixir-some-performance-remarks-4b5bd10202b6">Elixir. Some performance remarks</a> was originally published in <a href="https://medium.com/flatstack-blog">Flatstack Thoughts</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Massive data import options]]></title>
            <link>https://medium.com/flatstack-blog/massive-data-import-options-a33ea4fa1339?source=rss-c8a01aca7492------2</link>
            <guid isPermaLink="false">https://medium.com/p/a33ea4fa1339</guid>
            <category><![CDATA[database]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[data-analysis]]></category>
            <category><![CDATA[flatstack]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <dc:creator><![CDATA[Dmitry Trager]]></dc:creator>
            <pubDate>Fri, 31 Aug 2018 14:35:18 GMT</pubDate>
            <atom:updated>2018-08-31T14:35:18.294Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U6M2S9os43zeG4PUSd-PUQ.jpeg" /><figcaption>Image from <a href="https://studio.envato.com/">https://studio.envato.com/</a></figcaption></figure><p>Let’s suppose you have a huge chunk of data in file (csv, for example): tens of thousands of rows. And you need to import it in your DB. Let’s also suppose that your application lives on <a href="https://heroku.com/">Heroku</a>. How would you do that? Let’s go from more obvious to less obvious.</p><p>If there are no sensitive data, you may want to attach data files to your repo and then import it from rake task or even from database <a href="https://edgeguides.rubyonrails.org/active_record_migrations.html#migrations-and-seed-data">seeds</a>, for example.</p><ol><li><strong>You go through the rows and create DB records one by one.</strong></li></ol><p>Main problem here is time. Inserting of so many records will take a lot of time. And one-off dyno that is going to do this for you (when you run related task) has <a href="https://devcenter.heroku.com/articles/one-off-dynos#one-off-dyno-timeout">one hour timeout</a>. So you may fail here (if you have enough data to import) very easy.</p><p>2. <strong>You go through the rows, collect data and then create DB record all at once.</strong></p><p>Ok, let’s try to combine all inserts in one. This still can take a lot of time, but let’s think that timeout is not a problem anymore. Next problem you can face is lack of memory. When you collect data for your request, you may spend a huge amount of memory that is going to be freed only when task is finished.</p><p>What if your dyno does not have enough memory? In this case you may not see any record being created at all. You will only see <a href="https://devcenter.heroku.com/articles/ruby-memory-use">R14 errors</a> in log. That’s why you may need next approach.</p><p><strong>3. You go through the rows, collect data in batches and create DB records in batches.</strong></p><p>Couple of options that were considered by me: plain sql and <a href="https://github.com/zdennis/activerecord-import">activerecord-import</a>. SQL itself is always an option. But the gem could be used even on previous step. Here it can be used as well: just import data with little chunks(*).</p><p><strong>4. You move this logic into scheduled task.</strong></p><p>There was an opinion, that since web dynos are usually loaded more heavily moving the whole process to worker dyno(**) may help. This does not seem to be true. As it was mentioned already, when you need to perform some task or work in console Heroku allocates separate instance for you. Properties of this instance (dyno) depend on your current setup, but this dyno is not loaded by something else. So you don’t have to “choose” between web and worker types.</p><p><em>* In my case reducing chuck size to 1000 was enough. I guess it’s reasonable to consider something between 100 and 1000.</em></p><p><em>** These are two separate instances on Heroku usually.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a33ea4fa1339" width="1" height="1" alt=""><hr><p><a href="https://medium.com/flatstack-blog/massive-data-import-options-a33ea4fa1339">Massive data import options</a> was originally published in <a href="https://medium.com/flatstack-blog">Flatstack Thoughts</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>