<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>RNDSOFT Technology Blog</title><subtitle>Software development / Ruby-on-Rails</subtitle><author><name>RNDSOFT Technology Blog</name></author><id>https://teletype.in/atom/rnds</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/rnds?offset=0"></link><link rel="alternate" type="text/html" href="https://blog.rnds.pro/?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rnds"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/rnds?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-07T23:05:25.199Z</updated><entry><id>rnds:swiftpm-dsl-modular-app</id><link rel="alternate" type="text/html" href="https://blog.rnds.pro/swiftpm-dsl-modular-app?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rnds"></link><title>Пишем декларативный Package.swift: DSL для модульной архитектуры iOS проекта</title><published>2026-03-18T05:37:29.356Z</published><updated>2026-03-18T05:37:29.356Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/76/e5/76e5ee6d-d5a4-48b6-a817-93a6e99b3852.png"></media:thumbnail><category term="i-os" label="iOS"></category><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/00/1f/001fce1a-2aaa-40f2-a910-c4fcbe67446b.png&quot;&gt;Swift Package Manager сегодня является стандартным инструментом для модульной архитектуры iOS-проектов. Он позволяет разделять код на независимые модули,
 ускорять сборку и явно описывать зависимости. Однако по мере роста проекта файл Package.swift часто превращается в длинный список строковых зависимостей:</summary><content type="html">
  &lt;figure id=&quot;kwTA&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/00/1f/001fce1a-2aaa-40f2-a910-c4fcbe67446b.png&quot; width=&quot;1043&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;wZeS&quot;&gt;Swift Package Manager сегодня является стандартным инструментом для модульной архитектуры iOS-проектов. Он позволяет разделять код на независимые модули,&lt;br /&gt; ускорять сборку и явно описывать зависимости. Однако по мере роста проекта файл Package.swift часто превращается в длинный список строковых зависимостей:&lt;/p&gt;
  &lt;pre id=&quot;em2W&quot; data-lang=&quot;swift&quot;&gt;.target(
    name: &amp;quot;SomeFeature&amp;quot;,
    dependencies: [
        &amp;quot;Core&amp;quot;,
        &amp;quot;UI&amp;quot;,
        &amp;quot;Resources&amp;quot;
    ]
)&lt;/pre&gt;
  &lt;p id=&quot;x0cK&quot;&gt;Меня всегда раздражала одна особенность Package.swift:&lt;/p&gt;
  &lt;p id=&quot;3Wxl&quot;&gt;мы описываем зависимости, но не описываем архитектуру, из-за этого:&lt;/p&gt;
  &lt;ul id=&quot;cFen&quot;&gt;
    &lt;li id=&quot;yTfv&quot;&gt;переименование модулей усложняется;&lt;/li&gt;
    &lt;li id=&quot;bpxv&quot;&gt;архитектурные правила не проверяются компилятором;&lt;/li&gt;
    &lt;li id=&quot;sRSk&quot;&gt;количество повторяющегося кода быстро растёт.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;ID6Y&quot;&gt;В этой статье вместо того, чтобы рассматривать Package.swift как простой конфигурационный файл, превратим его в типобезопасный DSL для модульной архитектуры, где:&lt;/p&gt;
  &lt;ul id=&quot;Wg3c&quot;&gt;
    &lt;li id=&quot;CDLQ&quot;&gt;модули описываются через enum;&lt;/li&gt;
    &lt;li id=&quot;LP7a&quot;&gt;фичи генерируются декларативно;&lt;/li&gt;
    &lt;li id=&quot;UckC&quot;&gt;архитектурные правила фиксируются в коде.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;BiZG&quot;&gt;В итоге объявление зависимостей будет выглядеть так:&lt;/p&gt;
  &lt;pre id=&quot;TU8G&quot; data-lang=&quot;swift&quot;&gt;Libraries.allCases.map { $0.info.buildDependency() }

Local.Core.target()
Local.UI.target(deps: [.module(.Core)])
Local.DI.target(deps: [.module(.Core)])
Local.Resources.target()

featureTargets(module: { .SomeFeature($0)}) &lt;/pre&gt;
  &lt;h2 id=&quot;z4Em&quot;&gt;Погнали!&lt;/h2&gt;
  &lt;p id=&quot;rsXT&quot;&gt;Ключевая особенность SwiftPM в том, что манифесты — это обычные Swift-файлы. Это значит, что мы можем использовать возможности языка для описания архитектуры, пусть и с некоторыми ограничениями.&lt;/p&gt;
  &lt;p id=&quot;HODr&quot;&gt;и при попытке сделать проект с многомодульной архитектурой я получал примерно следующее:&lt;/p&gt;
  &lt;pre id=&quot;KE83&quot; data-lang=&quot;swift&quot;&gt;.target(
    name: &amp;quot;NewsPresentation&amp;quot;,
    dependencies: [
        &amp;quot;NewsDomain&amp;quot;,
        &amp;quot;Core&amp;quot;,
        &amp;quot;UI&amp;quot;,
        &amp;quot;Resources&amp;quot;
    ]
)
.target(
    name: &amp;quot;NewsDomain&amp;quot;,
    dependencies: [
        &amp;quot;NewsData&amp;quot;,
        &amp;quot;Core&amp;quot;
    ]
)
.target(
    name: &amp;quot;NewsData&amp;quot;,
    dependencies: [
        &amp;quot;Core&amp;quot;
    ]
)&lt;/pre&gt;
  &lt;p id=&quot;prjB&quot;&gt;Если в проекте пять фич — это уже 15 объявлений target. Если десять — то 30.&lt;/p&gt;
  &lt;p id=&quot;myRl&quot;&gt;Используя DSL, ту же архитектуру можно выразить одной строкой:&lt;/p&gt;
  &lt;pre id=&quot;gWTh&quot; data-lang=&quot;swift&quot;&gt;featureTargets(module: { .News($0)})&lt;/pre&gt;
  &lt;p id=&quot;Mm4e&quot;&gt;при этом будут сгенерированы следующие слои:&lt;/p&gt;
  &lt;ul id=&quot;lwLd&quot;&gt;
    &lt;li id=&quot;o5c8&quot;&gt;News_Presentation&lt;/li&gt;
    &lt;li id=&quot;qJH6&quot;&gt;News_Domain&lt;/li&gt;
    &lt;li id=&quot;3EJx&quot;&gt;News_Data&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;wCbD&quot;&gt;с уже правильно настроенным графом зависимостей&lt;/p&gt;
  &lt;figure id=&quot;LTub&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://habrastorage.org/getpro/habr/upload_files/3b4/cbf/5b1/3b4cbf5b154174da7a74450b7fbc677f.png&quot; width=&quot;1429&quot; /&gt;
    &lt;figcaption&gt;пример схемы зависимостей фичи&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;ujPj&quot;&gt;Проектируем DSL&lt;/h2&gt;
  &lt;p id=&quot;Vq9T&quot;&gt;Основная идея очень простая: большинство модульных архитектур следуют предсказуемым шаблонам. Вместо того чтобы повторять эти шаблоны в Package.swift, мы можем описать их прямо на Swift.&lt;/p&gt;
  &lt;p id=&quot;58eJ&quot;&gt;&lt;strong&gt;Объявление сторонних библиотек:&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;sgIE&quot; data-lang=&quot;swift&quot;&gt;enum RemotePackages: CaseIterable {
    case Alamofire

    var spec: RemotePackageSpec {
        switch self {
        case .Alamofire:
            return .init(
                &amp;quot;https://github.com/Alamofire/Alamofire.git&amp;quot;,
                packageName: &amp;quot;Alamofire&amp;quot;,
                version: &amp;quot;5.10.0&amp;quot;
            )
        }
    }
}&lt;/pre&gt;
  &lt;p id=&quot;iJnZ&quot;&gt;Теперь список зависимостей можно сгенерировать декларативно:&lt;/p&gt;
  &lt;pre id=&quot;EbCQ&quot;&gt;RemotePackages.allCases.map { $0.info.buildDependency() }&lt;/pre&gt;
  &lt;p id=&quot;q8Qr&quot;&gt;&lt;strong&gt;Объявление локальных модулей:&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;Ryvi&quot; data-lang=&quot;swift&quot;&gt;enum Local {
    case Core
    case UI
    case DI
    case Resources
    case Networking
}&lt;/pre&gt;
  &lt;p id=&quot;GK87&quot;&gt;После этого объявление target выглядит так:&lt;/p&gt;
  &lt;pre id=&quot;hGOY&quot; data-lang=&quot;swift&quot;&gt;Local.Core.target()
Local.UI.target(deps: [.module(.Core)])
Local.DI.target(deps: [.module(.Core)])&lt;/pre&gt;
  &lt;p id=&quot;it1y&quot;&gt;&lt;strong&gt;Объявление feature-модулей&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;5snc&quot; data-lang=&quot;swift&quot;&gt;enum Local {
    case Core
    case UI
    case DI
    case Resources
    case Networking

    case News(_ layer: FeatureLayer) // обьявляем фича модуль
}&lt;/pre&gt;
  &lt;p id=&quot;msYY&quot;&gt;Фича состоит из нескольких слоев:&lt;/p&gt;
  &lt;pre id=&quot;y9DC&quot; data-lang=&quot;swift&quot;&gt;enum FeatureLayer: String {
    case Presentation
    case Domain
    case Data
}&lt;/pre&gt;
  &lt;p id=&quot;f6tY&quot;&gt;Эти слои будут использоваться для автоматической генерации target-модулей.&lt;br /&gt;Настоящая же &lt;strong&gt;ценность DSL&lt;/strong&gt; — в кодировании правил зависимостей между слоями:&lt;/p&gt;
  &lt;pre id=&quot;3p5d&quot; data-lang=&quot;swift&quot;&gt;func featureTargets(
    module: ( _ layer: FeatureLayer) -&amp;gt; Local,
    presentationExtra: [Target.Dependency] = [],
    domainExtra: [Target.Dependency] = [],
    dataExtra: [Target.Dependency] = []
) -&amp;gt; [Target] {

    let presentation = module(.Presentation)
    let domain = module(.Domain)
    let data = module(.Data)

    return [
        presentation.target(deps: [
            .module(domain.name),
            .module(.Core),
            .module(.UI),
            .module(.Resources)
        ] + presentationExtra),

        domain.target(deps: [
            .module(data.name),
            .module(.Core)
        ] + domainExtra),

        data.target(deps: [
            .module(.Core),
            .module(.Networking)
        ] + dataExtra)
    ]
}&lt;/pre&gt;
  &lt;p id=&quot;dbXB&quot;&gt;И теперь объявление feature-модуля выглядит так:&lt;/p&gt;
  &lt;pre id=&quot;BZJr&quot; data-lang=&quot;swift&quot;&gt;featureTargets(module: { .Authorisation($0)} ])
featureTargets(module: { .News($0)} )&lt;/pre&gt;
  &lt;h2 id=&quot;YBhH&quot;&gt;Настройка FeatureLayer&lt;/h2&gt;
  &lt;p id=&quot;NUfn&quot;&gt;Еще одно преимущество такого подхода — структура слоев полностью настраиваемая. Команда может выбирать ее в зависимости от архитектуры проекта.&lt;/p&gt;
  &lt;p id=&quot;1xFa&quot;&gt;Например, можно разделить фичу на API и реализацию:&lt;/p&gt;
  &lt;ul id=&quot;VMD2&quot;&gt;
    &lt;li id=&quot;Euic&quot;&gt;FeatureApi&lt;/li&gt;
    &lt;li id=&quot;UEKQ&quot;&gt;FeatureImpl&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;OrxW&quot;&gt;Или использовать более детализированную структуру, например VIPER:&lt;/p&gt;
  &lt;ul id=&quot;EvQf&quot;&gt;
    &lt;li id=&quot;xIMZ&quot;&gt;View&lt;/li&gt;
    &lt;li id=&quot;80i6&quot;&gt;Presenter&lt;/li&gt;
    &lt;li id=&quot;N7n2&quot;&gt;Interactor&lt;/li&gt;
    &lt;li id=&quot;H0I7&quot;&gt;Router&lt;/li&gt;
    &lt;li id=&quot;xqJK&quot;&gt;DataStore&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;cKgm&quot;&gt;Важно не количество слоев, а правила зависимостей между ними.&lt;br /&gt;Именно эти правила DSL позволяет зафиксировать в коде.&lt;/p&gt;
  &lt;p id=&quot;YtY3&quot;&gt;Полный текст package.swift&lt;/p&gt;
  &lt;pre id=&quot;heHT&quot; data-lang=&quot;swift&quot;&gt;// swift-tools-version: 5.9
import PackageDescription
import Foundation

// MARK: - Declarations
enum ProjectPaths {
    static let sources = &amp;quot;Sources&amp;quot;
}

// MARK: Local Modules
enum Local {
    case Core
    case UI
    case DI
    case Resources
    case Networking
    
    case Router(_ layer: FeatureImplLayer)
    case MainScreen(_ layer: FeatureLayer)
    case DetailScreen(_ layer: FeatureLayer)
    
}

// MARK: Remote Packages
enum RemotePackages: CaseIterable {
    case Alamofire
    var spec: RemotePackageSpec {
        switch self {
         case .Alamofire:
             return .init(
                 &amp;quot;https://github.com/Alamofire/Alamofire.git&amp;quot;,
                 packageName: &amp;quot;Alamofire&amp;quot;,
                 version: &amp;quot;5.8.0&amp;quot;
             )
        }
    }
}

// MARK: Feature Layering System (Optional)
enum FeatureLayer: String {
    case Presentation, Domain, Data
}

enum FeatureImplLayer: String {
    case Impl, Api
}

// MARK: - Package Formation 
let packageName = &amp;quot;DemoApp&amp;quot;
let package = buildPackage(
    name: packageName,
    defaultLocalization: &amp;quot;en&amp;quot;,
    platforms: [.iOS(.v15)]
) {
    [
        Local.Router(.Impl).product(),
        Local.Router(.Api).product(),
        Local.Core.product()
    ]
} dependencies: {
    RemotePackages.allCases.map { $0.spec.buildDependency() }
} targets: {
    // Base modules
    Local.Networking.target(deps: [.module(.Core), .library(.Alamofire)])
    Local.UI.target(deps: [.module(.Core)])
    Local.DI.target(deps: [.module(.Core)])
    Local.Core.target()
    
    Local.Resources.target(resources: [
        .process(&amp;quot;Resources.xcassets&amp;quot;)
    ])
    
    featureTargets(module: { .Router($0) }, implementationExtra: [
        .module(.MainScreen(.Presentation)),
        .module(.DetailScreen(.Presentation))
    ])
    featureTargets(module: { .MainScreen($0)},
                   presentationExtra:  [ .module(Local.DI)] )
    
    featureTargets(module: { .DetailScreen($0)},
                   presentationExtra:  [ .module(Local.DI)] )
}

// MARK: - Feature configuration
func featureTargets(
    module: ( _ layer: FeatureLayer) -&amp;gt; Local,
    presentationExtra: [Target.Dependency] = [],
    domainExtra: [Target.Dependency] = [],
    dataExtra: [Target.Dependency] = []
) -&amp;gt; [Target] {

    let presentation = module(.Presentation)
    let domain = module(.Domain)
    let data = module(.Data)

    return [
        presentation.target(deps: [
            .module(domain.name),
            .module(.Core),
            .module(.UI),
            .module(.Resources)
        ] + presentationExtra),

        domain.target(deps: [
            .module(data.name),
            .module(.Core)
        ] + domainExtra),

        data.target(deps: [
            .module(.Core),
            .module(.Networking)
        ] + dataExtra)
    ]
}

func featureTargets(
    module: ( _ layer: FeatureImplLayer) -&amp;gt; Local,
    implementationExtra: [Target.Dependency] = [],
    apiExtra: [Target.Dependency] = []
) -&amp;gt; [Target] {

    let implementation = module(.Impl)
    let api = module(.Api)
    
    return [
        implementation.target(deps: [
            .module(api),
            .module(.Core)
        ] + implementationExtra),
        api.target(deps: apiExtra)
    ]
}
// DSL PART 
// MARK: - Helpers превращает enum Local в рабочее описание модуля
extension Local {
    var name: String {
        let parsed = parsedDescription
        if let layer = parsed.layer {
            return &amp;quot;\(parsed.base)_\(layer)&amp;quot;
        }
        return parsed.base
    }
    
    private var path: String {
        let parsed = parsedDescription
        if let layer = parsed.layer {
            return &amp;quot;\(ProjectPaths.sources)/Features/\(parsed.base)/\(layer)&amp;quot;
        }
        return &amp;quot;\(ProjectPaths.sources)/\(parsed.base)&amp;quot;
    }
    
    private func module(_ resources: [Resource]?) -&amp;gt; TargetSpec {
       return TargetSpec(name: name, path: path, resources: resources)
    }
    private var module: TargetSpec { TargetSpec(name: name, path: path) }
    
    func target(deps: [Target.Dependency] = [], resources: [Resource]? = nil) -&amp;gt; Target {
        module(resources).target(deps: deps)
    }
    
    func product() -&amp;gt; Product {
        module.product()
    }
}

//Упрощает объявление зависимостей
extension Target.Dependency {
    static func module(_ m: Local) -&amp;gt; Target.Dependency {
        .target(name: m.name)
    }
    
    static func module(_ name: String) -&amp;gt; Target.Dependency {
        .target(name: name)
    }
    
    static func library(_ lib: RemotePackages) -&amp;gt; Target.Dependency {
        .product(name: lib.spec.productName,
                 package: lib.spec.packageName)
    }
}

// MARK: - DSL Core
// Позволяет декларативно собирать список Target
@resultBuilder
enum TargetsBuilder {
    static func buildBlock(_ parts: [Target]...) -&amp;gt; [Target] {
        parts.flatMap { $0 }
    }
    static func buildExpression(_ t: Target) -&amp;gt; [Target] { [t] }
    static func buildExpression(_ ts: [Target]) -&amp;gt; [Target] { ts }
}
// Позволяет декларативно собирать список Product
@resultBuilder
enum ProductsBuilder {
    static func buildBlock(_ parts: [Product]...) -&amp;gt; [Product] {
        parts.flatMap { $0 }
    }
    static func buildExpression(_ p: Product) -&amp;gt; [Product] { [p] }
    static func buildExpression(_ ps: [Product]) -&amp;gt; [Product] { ps }
}
// Позволяет декларативно собирать список Package.Dependency.
@resultBuilder
enum DependenciesBuilder {
    static func buildBlock(_ parts: [Package.Dependency]...) -&amp;gt; [Package.Dependency] {
        parts.flatMap { $0 }
    }
    static func buildExpression(_ d: Package.Dependency) -&amp;gt; [Package.Dependency] { [d] }
    static func buildExpression(_ ds: [Package.Dependency]) -&amp;gt; [Package.Dependency] { ds }
}
// Обёртка над Package, чтобы собирать package через твой DSL
func buildPackage(
    name: String,
    defaultLocalization: LanguageTag? = nil,
    platforms: [SupportedPlatform] = [],
    @ProductsBuilder products: () -&amp;gt; [Product],
    @DependenciesBuilder dependencies: () -&amp;gt; [Package.Dependency],
    @TargetsBuilder targets: () -&amp;gt; [Target]
) -&amp;gt; Package {
    PackageSpec(
        name: name,
        defaultLocalization: defaultLocalization,
        platforms: platforms,
        products: products(),
        dependencies: dependencies(),
        targets: targets()
    ).build()
}

// MARK: - Specs
// Промежуточная модель для сборки Package
struct PackageSpec {
    var name: String
    var defaultLocalization: LanguageTag?
    var platforms: [SupportedPlatform] = []
    var products: [Product] = []
    var dependencies: [Package.Dependency] = []
    var targets: [Target] = []
    
    func build() -&amp;gt; Package {
        Package(
            name: name,
            defaultLocalization: defaultLocalization,
            platforms: platforms,
            products: products,
            dependencies: dependencies,
            targets: targets
        )
    }
}
// Модель для описания внешнего пакета
struct RemotePackageSpec {
    let url: String
    let packageName: String
    let productName: String
    let version: Version
    
    init(_ url: String,
         packageName: String,
         productName: String? = nil,
         version: Version) {
        self.url = url
        self.packageName = packageName
        self.productName = productName ?? packageName
        self.version = version
    }
    
    func buildDependency() -&amp;gt; Package.Dependency {
        .package(url: url, from: version)
    }
}
// Модель для описания target
struct TargetSpec {
    private let name: String
    private let path: String
    private let resources: [Resource]?
    init(name: String, path: String, resources: [Resource]? = nil) {
        self.name = name
        self.path = path
        self.resources = resources
    }
    
    func target(deps: [Target.Dependency] = []) -&amp;gt; Target {
        .target(
            name: name,
            dependencies: deps,
            path: path,
            resources: resources
        )
    }
    
    func testTarget(deps: [Target.Dependency] = []) -&amp;gt; Target {
        .testTarget(
            name: name,
            dependencies: deps,
            path: path
        )
    }
    
    func product() -&amp;gt; Product {
        .library(name: name, targets: [name])
    }
}

// MARK: - Helper to automatically generate the feature path and name for import
extension Local {
    private var parsedDescription: (base: String, layer: String?) {
        let description = String(describing: self)

        guard
            let start = description.firstIndex(of: &amp;quot;(&amp;quot;),
            let end = description.firstIndex(of: &amp;quot;)&amp;quot;)
        else {
            return (description, nil)
        }

        let base = String(description[..&amp;lt;start])
        var layer = String(description[description.index(after: start)..&amp;lt;end])

        // remove type prefix like &amp;quot;Main.FeatureLayer.&amp;quot;
        if let last = layer.split(separator: &amp;quot;.&amp;quot;).last {
            layer = String(last).capitalizedFirst
        }

        return (base, layer)
    }
}

extension String {
    var capitalizedFirst: String {
        prefix(1).uppercased() + dropFirst()
    }
}
&lt;/pre&gt;
  &lt;p id=&quot;uITh&quot;&gt;Все что используется для настройки проекта, находится до &amp;quot;DSL PART&amp;quot;&lt;/p&gt;
  &lt;h2 id=&quot;5y2n&quot;&gt;Код демо-проекта&lt;/h2&gt;
  &lt;p id=&quot;1vmC&quot;&gt;Если хотите попробовать DSL в реальном проекте:&lt;/p&gt;
  &lt;p id=&quot;ps9Y&quot;&gt;&lt;a href=&quot;https://github.com/paliarmo/modular-dsl-spm&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;0yyu&quot;&gt;P.S. Архитектура в каждой команде &amp;quot;немного&amp;quot; своя. Поэтому, пример в статье намеренно упрощён — чтобы было легче увидеть саму идею DSL.&lt;/p&gt;

</content></entry><entry><id>rnds:068-developer.2.0</id><link rel="alternate" type="text/html" href="https://blog.rnds.pro/068-developer.2.0?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rnds"></link><title>Developer 2.0: почему Big Tech автоматизирует не то</title><published>2026-01-21T09:52:52.625Z</published><updated>2026-01-21T09:52:52.625Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/ce/d6/ced6200b-7d8a-4c37-ad77-f32168279823.png"></media:thumbnail><category term="mobile-development" label="mobile development"></category><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/f9/bd/f9bd20f1-a2e6-48e8-8cef-67d3b80f68d3.png&quot;&gt;Big Tech любит говорить, что ИИ «меняет разработку».</summary><content type="html">
  &lt;figure id=&quot;a2In&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/f9/bd/f9bd20f1-a2e6-48e8-8cef-67d3b80f68d3.png&quot; width=&quot;1043&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;nlcK&quot;&gt;Big Tech любит говорить, что ИИ «меняет разработку».&lt;/p&gt;
  &lt;p id=&quot;MUhZ&quot;&gt;Он пишет код.&lt;br /&gt;Он ускоряет delivery.&lt;br /&gt;Он «повышает продуктивность разработчиков».&lt;/p&gt;
  &lt;p id=&quot;74Ze&quot;&gt;Но если посмотреть внимательнее, становится заметно другое:&lt;br /&gt;мы автоматизируем не самую дорогую часть процесса.&lt;/p&gt;
  &lt;p id=&quot;Rnhe&quot;&gt;Под «стоимостью разработки» дальше я буду понимать не деньги как таковые и не абстрактную «сложность», а совокупность времени, внимания и когнитивной нагрузки, которые тратятся на принятие решений внутри команды.&lt;/p&gt;
  &lt;p id=&quot;m6t5&quot;&gt;Это не только время конкретного разработчика, но и стоимость переключений контекста, синхронизации людей и задержек между обнаружением проблемы и осмысленным решением. В этом смысле самая дорогая часть разработки — не написание кода, а интерпретация сигналов и выбор между допустимыми вариантами.&lt;/p&gt;
  &lt;p id=&quot;DAuN&quot;&gt;Именно здесь инструменты почти не эволюционировали.&lt;/p&gt;
  &lt;h2 id=&quot;jheI&quot;&gt;Автоматизация не там, где болит&lt;/h2&gt;
  &lt;p id=&quot;O6px&quot;&gt;Современная разработка построена вокруг бинарных систем:&lt;/p&gt;
  &lt;ul id=&quot;36Al&quot;&gt;
    &lt;li id=&quot;Q2N7&quot;&gt;правило либо выполняется, либо нет;&lt;/li&gt;
    &lt;li id=&quot;9OLb&quot;&gt;проверка либо проходит, либо падает;&lt;/li&gt;
    &lt;li id=&quot;Cf0W&quot;&gt;CI либо пускает merge, либо блокирует его.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;JiOR&quot;&gt;Big Tech довёл эту модель до технического совершенства. Но когнитивного масштабирования так и не произошло.&lt;/p&gt;
  &lt;p id=&quot;Nb6O&quot;&gt;По мере роста проектов происходит одно и то же:&lt;/p&gt;
  &lt;ul id=&quot;63Lp&quot;&gt;
    &lt;li id=&quot;tuVC&quot;&gt;добавляются новые линтеры;&lt;/li&gt;
    &lt;li id=&quot;UXgX&quot;&gt;новые политики;&lt;/li&gt;
    &lt;li id=&quot;Vuel&quot;&gt;новые best practices;&lt;/li&gt;
    &lt;li id=&quot;pBMr&quot;&gt;новые запреты.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Dgli&quot;&gt;Каждый из них по отдельности логичен. &lt;br /&gt;В сумме — они превращают разработку в постоянную интерпретацию сигналов.&lt;br /&gt;Инструменты говорят: «Это неправильно». &lt;br /&gt;И на этом их ответственность заканчивается.&lt;/p&gt;
  &lt;h2 id=&quot;j7ns&quot;&gt;Проблема, которую чувствовал каждый&lt;/h2&gt;
  &lt;p id=&quot;ICfx&quot;&gt;По мере роста проектов количество правил неизбежно увеличивается.&lt;/p&gt;
  &lt;p id=&quot;QPHF&quot;&gt;Мы добавляем линтеры, style guides, CI-проверки, политики, дополнительные гейты. &lt;br /&gt;Всё это необходимо. &lt;br /&gt;И всё это работает...&lt;br /&gt;До определённого момента.&lt;/p&gt;
  &lt;p id=&quot;esrP&quot;&gt;Потому что большинство инструментов отвечают только на один вопрос:&lt;br /&gt;Это допустимо или нет?&lt;/p&gt;
  &lt;p id=&quot;iJpR&quot;&gt;Они отлично находят ошибки. Но почти не помогают понять, что делать дальше. Этот недостающий шаг перекладывается на разработчика. &lt;br /&gt;Каждое предупреждение становится задачей на интерпретацию. &lt;br /&gt;Каждое нарушение требует контекста. &lt;br /&gt;Каждая упавшая проверка превращается в решение, с которым инструмент не помогает. &lt;br /&gt;Со временем это накапливается — не как одна большая проблема, а как постоянная фоновая нагрузка.&lt;/p&gt;
  &lt;p id=&quot;j3pi&quot;&gt;Мы больше не просто пишем код.&lt;/p&gt;
  &lt;p id=&quot;2Aum&quot;&gt;Мы:&lt;/p&gt;
  &lt;ul id=&quot;10Iw&quot;&gt;
    &lt;li id=&quot;KF7L&quot;&gt;переводим правила в намерения;&lt;/li&gt;
    &lt;li id=&quot;2tU6&quot;&gt;превращаем гайдлайны в решения;&lt;/li&gt;
    &lt;li id=&quot;rNaH&quot;&gt;вручную связываем сигналы, которые никогда не проектировались для связи.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;chgL&quot;&gt;Именно здесь возникает реальная стоимость разработки — во времени, внимании и синхронизации людей.&lt;/p&gt;
  &lt;p id=&quot;tWd7&quot;&gt;Дальше я буду использовать две условные модели - Developer 1.0 и Developer 2.0. &lt;br /&gt;Эти модели показывают два разных подхода к принятию решений в разработке. Они намеренно упрощены - чтобы проиллюстрировать предел текущей инструментальной модели.&lt;/p&gt;
  &lt;h2 id=&quot;WL3z&quot;&gt;Developer 1.0 — модель, на которой индустрия застряла&lt;/h2&gt;
  &lt;p id=&quot;pnPu&quot;&gt;Developer 1.0 — это инженер, работающий в мире статических правил:&lt;/p&gt;
  &lt;ul id=&quot;NDsB&quot;&gt;
    &lt;li id=&quot;8Gg9&quot;&gt;детерминированная логика;&lt;/li&gt;
    &lt;li id=&quot;sVYt&quot;&gt;формальные условия;&lt;/li&gt;
    &lt;li id=&quot;ppBt&quot;&gt;предсказуемый результат.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Fxbz&quot;&gt;Эта модель не устарела. Она перестала быть достаточной в условиях растущей сложности систем.&lt;/p&gt;
  &lt;p id=&quot;BndB&quot;&gt;Статический анализ, линтеры, style guides, CI — всё это построено вокруг идеи: чёткое правило даёт чёткий результат.&lt;/p&gt;
  &lt;p id=&quot;ifM8&quot;&gt;Эта модель - фундамент в текущей разработке.&lt;/p&gt;
  &lt;p id=&quot;8wkC&quot;&gt;Я вижу проблему в другом: мы продолжали наращивать правила, не меняя способ принятия решений.&lt;/p&gt;
  &lt;h2 id=&quot;xYVo&quot;&gt;Где статические правила перестают масштабироваться&lt;/h2&gt;
  &lt;p id=&quot;es4i&quot;&gt;В какой-то момент правила перестают быть защитой и становятся просто сигналами. Линтер ругается на хардкодную строку. Анализатор жалуется на сложность. Гайдлайн запрещает «плохой паттерн».&lt;/p&gt;
  &lt;p id=&quot;5JZ1&quot;&gt;Формально — всё верно.&lt;br /&gt;Практически — недостаточно.&lt;/p&gt;
  &lt;p id=&quot;GrWy&quot;&gt;Потому что правило не понимает намерения.&lt;br /&gt;Оно не знает:&lt;/p&gt;
  &lt;ul id=&quot;V46F&quot;&gt;
    &lt;li id=&quot;MGM2&quot;&gt;где используется строка;&lt;/li&gt;
    &lt;li id=&quot;Xt3R&quot;&gt;что видит пользователь;&lt;/li&gt;
    &lt;li id=&quot;XgTE&quot;&gt;почему решение было принято именно так;&lt;/li&gt;
    &lt;li id=&quot;SqXW&quot;&gt;насколько это критично именно сейчас.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;5qdb&quot;&gt;Интерпретация снова ложится на разработчика.&lt;br /&gt;Каждая проверка предполагает, что человек сам «соединит точки».&lt;br /&gt;Здесь статические правила перестают масштабироваться — не технически, а когнитивно.&lt;/p&gt;
  &lt;h2 id=&quot;NwPQ&quot;&gt;Developer 2.0 — поверх логики&lt;/h2&gt;
  &lt;p id=&quot;6nHK&quot;&gt;Developer 2.0 — это модель, в которой разработчик больше не принимает норму, что детерминированная логика и формальные условия — единственное, с чем он работает.&lt;/p&gt;
  &lt;p id=&quot;WgNU&quot;&gt;Он начинает работать с интерпретационным слоем, расположенным между формальным сигналом и инженерным решением.&lt;/p&gt;
  &lt;p id=&quot;3Cwx&quot;&gt;Он не ждёт, что ИИ «напишет код за него».&lt;br /&gt;Он требует другого:&lt;/p&gt;
  &lt;ul id=&quot;2dGk&quot;&gt;
    &lt;li id=&quot;yJtM&quot;&gt;объяснять сигналы, а не просто фиксировать нарушения;&lt;/li&gt;
    &lt;li id=&quot;IA3h&quot;&gt;связывать контекст, а не механически запрещать;&lt;/li&gt;
    &lt;li id=&quot;19aQ&quot;&gt;сокращать путь от обнаружения проблемы к осмысленному решению.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;GeWX&quot;&gt;В своей статье я буду показывать это на примере с CI, он — самый наглядный, но далеко не единственный. &lt;br /&gt;Интерпретационный слой — это не фича линтера, а вполне архитектурный приём.&lt;/p&gt;
  &lt;p id=&quot;nj6q&quot;&gt;Он может быть встроен:&lt;/p&gt;
  &lt;ul id=&quot;iqqj&quot;&gt;
    &lt;li id=&quot;nN76&quot;&gt;в бизнес-логику;&lt;/li&gt;
    &lt;li id=&quot;0lzs&quot;&gt;в слои валидации;&lt;/li&gt;
    &lt;li id=&quot;7F1Z&quot;&gt;в правила принятия решений внутри системы.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;fjzf&quot;&gt;Везде, где сегодня есть жёсткое условие, может появиться слой интерпретации, работающий поверх логики, а не вместо неё.&lt;/p&gt;
  &lt;h2 id=&quot;cCjG&quot;&gt;Пример: хардкодные строки&lt;/h2&gt;
  &lt;p id=&quot;Tpw5&quot;&gt;Хардкодные строки — классическая проблема мобильных проектов.&lt;br /&gt;Линтер прав: их быть не должно.&lt;/p&gt;
  &lt;p id=&quot;3SCA&quot;&gt;Это намеренно упрощённый пример. Не потому, что он критичен сам по себе, а потому, что на нём наглядно видна разница подходов.&lt;/p&gt;
  &lt;p id=&quot;myLJ&quot;&gt;Developer 1.0:&lt;/p&gt;
  &lt;ul id=&quot;Fvja&quot;&gt;
    &lt;li id=&quot;nB2s&quot;&gt;обнаружил нарушение;&lt;/li&gt;
    &lt;li id=&quot;zRB3&quot;&gt;сообщил о проблеме;&lt;/li&gt;
    &lt;li id=&quot;mOWP&quot;&gt;переложил всю интерпретацию на разработчика.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;karU&quot;&gt;Developer 2.0&lt;/p&gt;
  &lt;ul id=&quot;4mQF&quot;&gt;
    &lt;li id=&quot;uIzp&quot;&gt;обнаружил нарушение;&lt;/li&gt;
    &lt;li id=&quot;ICxd&quot;&gt;проанализировал место использования;&lt;/li&gt;
    &lt;li id=&quot;4DpM&quot;&gt;учёл UI-контекст;&lt;/li&gt;
    &lt;li id=&quot;bNB4&quot;&gt;проверил, есть ли уже в ресурсах подобные строки;&lt;/li&gt;
    &lt;li id=&quot;kjZo&quot;&gt;предложил варианты нейминга;&lt;/li&gt;
    &lt;li id=&quot;IyHm&quot;&gt;сопоставил с существующими решениями в коде;&lt;/li&gt;
    &lt;li id=&quot;IRNx&quot;&gt;опционально можно сделать автозамену тех вариантов, что выберет разработчик.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;I2B4&quot;&gt;Нарушение остаётся нарушением. Но путь от сигнала к решению становится короче и понятнее.&lt;/p&gt;
  &lt;p id=&quot;gQSc&quot;&gt;ИИ это умеет. &lt;br /&gt;Большинство инструментов — до сих пор нет.&lt;/p&gt;
  &lt;p id=&quot;2I2I&quot;&gt;Важно уточнить: для реализации такого интерпретационного шага не требуется ни облачный ИИ, ни тяжёлая инфраструктура.&lt;/p&gt;
  &lt;p id=&quot;ty7Y&quot;&gt;В моём случае для подобной проверки было достаточно локального запуска через &lt;strong&gt;LM Studio&lt;/strong&gt; и легковесной модели уровня &lt;strong&gt;Qwen&lt;/strong&gt; или даже &lt;strong&gt;LLaMA-2 7B (GGUF)&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;ZbgH&quot;&gt;Речь не идёт о генерации кода «с нуля». Модель используется для анализа уже существующего кода и ресурсов проекта, включая просмотр файлов репозитория, но в строго ограниченной, целенаправленной задаче — &lt;strong&gt;сопровождении конкретного инженерного решения.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;Nj1b&quot;&gt;Это делает стоимость такого интерпретационного слоя принципиально иной: &lt;br /&gt;он не требует передачи коммерческого контекста во внешний сервис&lt;br /&gt;и может быть встроен локально — как инструмент сопровождения решений, а не как автономный агент.&lt;/p&gt;
  &lt;h2 id=&quot;rUea&quot;&gt;Вывод&lt;/h2&gt;
  &lt;p id=&quot;KqeX&quot;&gt;Подход Developer 2.0 — это не про ИИ.&lt;br /&gt;Это про сдвиг ответственности в разработке.&lt;/p&gt;
  &lt;p id=&quot;l8sn&quot;&gt;Разработчик больше не отвечает только за написание кода и настройку строгих инструментов его сохранности.&lt;/p&gt;
  &lt;p id=&quot;6p6C&quot;&gt;Он также проектирует интерпретационный слой — набор механизмов, которые связывают формальные сигналы системы с инженерным решением, сохраняя когнитивный контекст, договоренности и гайдлайны там, где правила и проверки перестают быть самодостаточными.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p id=&quot;QYYz&quot;&gt;&lt;em&gt;P.S.  В следующей части я разберу другой побочный эффект этой модели: ситуацию, в которой формально «зелёный» CI начинает подменять инженерное качество и постепенно разрушает само мышление, на котором это качество должно держаться.&lt;/em&gt;&lt;/p&gt;

</content></entry><entry><id>rnds:067-protocol-transfer-data</id><link rel="alternate" type="text/html" href="https://blog.rnds.pro/067-protocol-transfer-data?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rnds"></link><title>Обзор протоколов обработки и хранения данных</title><published>2025-05-13T17:30:31.360Z</published><updated>2025-06-10T10:51:37.834Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/46/b2/46b2aaa8-fffe-4d24-8058-cba3f8543967.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/a9/43/a943ed2f-78f0-496e-ac7a-a7435f364264.jpeg&quot;&gt;And now for something completely different....                                                  Летающий цирк Монти Пайтона о формате Parquet</summary><content type="html">
  &lt;blockquote id=&quot;5aj4&quot; data-align=&quot;right&quot;&gt;And now for something completely different....                                                  Летающий цирк Монти Пайтона о формате Parquet&lt;/blockquote&gt;
  &lt;figure id=&quot;IW0u&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a9/43/a943ed2f-78f0-496e-ac7a-a7435f364264.jpeg&quot; width=&quot;1043&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;zR7X&quot;&gt;Введение&lt;/h3&gt;
  &lt;p id=&quot;2E85&quot;&gt;В ландшафте современной инженерии данных выбор правильного протокола сериализации и обработки данных является критическим решением, которое влияет на производительность, взаимодействие и масштабируемость системы. Эти протоколы определяют, как данные структурируются, сериализуются, хранятся и передаются между различными системами в различных языках программирования.&lt;/p&gt;
  &lt;p id=&quot;ecLP&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;BqcN&quot;&gt;В этой статье мы рассмотрим пять протоколов обработки данных, которые стали отраслевыми стандартами: MessagePack, Apache Avro, Protocol Buffers (protobuf), Apache Thrift и Apache Parquet. Каждый из них предлагает определенные преимущества и оптимизирован для конкретных случаев использования: от коммуникации в реальном времени до аналитики больших данных.&lt;/p&gt;
  &lt;p id=&quot;eLEw&quot;&gt;Независимо от того, создаете ли вы микросервисы, проектируете конвейеры данных или внедряете аналитические системы, понимание этих протоколов поможет вам принимать обоснованные архитектурные решения.&lt;/p&gt;
  &lt;p id=&quot;0apr&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;MCTY&quot;&gt;MessagePack&lt;/h2&gt;
  &lt;h3 id=&quot;AM0i&quot;&gt;Общее описание&lt;/h3&gt;
  &lt;p id=&quot;NGEU&quot;&gt;MessagePack — это формат бинарной сериализации, который описывает себя как &amp;quot;JSON, но быстрый и компактный&amp;quot;. MessagePack обеспечивает эффективное бинарное представление данных, оптимизированное для передачи и хранения. В отличие от JSON, MessagePack предлагает значительные улучшения как в размере, так и в скорости обработки.&lt;/p&gt;
  &lt;h3 id=&quot;92Gz&quot;&gt;Ключевые концепции реализации&lt;/h3&gt;
  &lt;ul id=&quot;0tRR&quot;&gt;
    &lt;li id=&quot;sjZ8&quot;&gt;Бинарный формат: данные кодируются в компактном бинарном формате, а не в текстовом.&lt;/li&gt;
    &lt;li id=&quot;48mE&quot;&gt;Отсутствие схемы: предварительно определенная схема не требуется, подобно JSON.&lt;/li&gt;
    &lt;li id=&quot;03jW&quot;&gt;Сохранение типов: автоматически сохраняет типы данных при сериализации.&lt;/li&gt;
    &lt;li id=&quot;b2vo&quot;&gt;Минимальные накладные расходы: разработан для минимизации затрат на сериализацию.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;385G&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;IGy3&quot;&gt;Поддерживаемые типы данных&lt;/h3&gt;
  &lt;p id=&quot;Jvpe&quot;&gt;MessagePack &lt;a href=&quot;https://github.com/msgpack/msgpack/blob/master/spec.md#type-system&quot; target=&quot;_blank&quot;&gt;поддерживает&lt;/a&gt; большое кол-во различных типов данных:&lt;/p&gt;
  &lt;ul id=&quot;cVue&quot;&gt;
    &lt;li id=&quot;0NSj&quot;&gt;Nil (null)&lt;/li&gt;
    &lt;li id=&quot;CXPB&quot;&gt;Boolean (логический)&lt;/li&gt;
    &lt;li id=&quot;YvI0&quot;&gt;Integer (целое число, со знаком/без знака, 8/16/32/64-бит)&lt;/li&gt;
    &lt;li id=&quot;8nQ1&quot;&gt;Float (с плавающей точкой, 32/64-бит)&lt;/li&gt;
    &lt;li id=&quot;PP9X&quot;&gt;Raw bytes (сырые байты, бинарный формат)&lt;/li&gt;
    &lt;li id=&quot;QLqs&quot;&gt;String (строка, UTF-8)&lt;/li&gt;
    &lt;li id=&quot;5OMs&quot;&gt;Array (массив)&lt;/li&gt;
    &lt;li id=&quot;riW8&quot;&gt;Map (пары ключ-значение)&lt;/li&gt;
    &lt;li id=&quot;ZgFl&quot;&gt;&lt;a href=&quot;https://github.com/msgpack/msgpack/blob/master/spec.md#extension-types&quot; target=&quot;_blank&quot;&gt;Extension types&lt;/a&gt; (типы расширений для пользовательских структур данных)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;jaeq&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;9beV&quot;&gt;Использование&lt;/h3&gt;
  &lt;p id=&quot;ILSX&quot;&gt;MessagePack отлично подходит в сценариях, где:&lt;/p&gt;
  &lt;ul id=&quot;SzoQ&quot;&gt;
    &lt;li id=&quot;QCYo&quot;&gt;вам нужна более эффективная альтернатива JSON,&lt;/li&gt;
    &lt;li id=&quot;gzgM&quot;&gt;коммуникация в реальном времени требует компактных сообщений, в т.ч. взаимодействие IoT устройств, а также передача большого количества данных по сети,&lt;/li&gt;
    &lt;li id=&quot;mrUh&quot;&gt;важна гибкость схемы.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;rWCK&quot;&gt;MessagePack используется в Redis в качестве протокола сериализации данных, а также в Fluentd в качестве внутреннего представления данных, увеличивая производительность при передаче логов.&lt;/p&gt;
  &lt;h3 id=&quot;jnKo&quot;&gt;Полезные рекомендации&lt;/h3&gt;
  &lt;p id=&quot;82qi&quot;&gt;1. Используйте для простых структур: MessagePack работает лучше всего с относительно плоскими структурами данных.&lt;/p&gt;
  &lt;p id=&quot;48ZA&quot;&gt;2. Используйте сжатие: для больших нагрузок комбинируйте MessagePack с алгоритмами сжатия.&lt;/p&gt;
  &lt;p id=&quot;2q58&quot;&gt;3. Версионируйте свои данные: включайте поле версии в структуру данных для обработки изменений формата.&lt;/p&gt;
  &lt;p id=&quot;roFn&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;aU6e&quot;&gt;Apache Avro&lt;/h2&gt;
  &lt;h3 id=&quot;EJ87&quot;&gt;Общее описание&lt;/h3&gt;
  &lt;p id=&quot;6Wby&quot;&gt;Apache Avro — это система сериализации данных, разработанная в рамках проекта Apache Hadoop. Apache Avro обеспечивает компактный, быстрый, бинарный формат данных с богатыми структурами данных и формат файла-контейнера для хранения метаданных. Одной из отличительных особенностей Avro является его надежные возможности эволюции схемы, что делает его идеальным для систем, где структуры данных изменяются со временем.&lt;/p&gt;
  &lt;p id=&quot;jS12&quot;&gt;Ключевые концепции реализации:&lt;/p&gt;
  &lt;ul id=&quot;KdHL&quot;&gt;
    &lt;li id=&quot;v87s&quot;&gt;На основе схемы: требует JSON-схемы для определения структуры данных. Файлы данных включают свою схему, что делает их самодокументируемыми.&lt;/li&gt;
    &lt;li id=&quot;Qe7b&quot;&gt;Эволюция схемы: поддерживает добавление, удаление и изменение полей с сохранением совместимости.&lt;/li&gt;
    &lt;li id=&quot;EPvd&quot;&gt;Бинарное кодирование: использует компактное бинарное кодирование для эффективного хранения и передачи.&lt;/li&gt;
    &lt;li id=&quot;cu35&quot;&gt;Богатые структуры данных: поддерживает сложные вложенные типы данных.&lt;/li&gt;
    &lt;li id=&quot;FVxR&quot;&gt;&lt;a href=&quot;https://avro.apache.org/docs/1.12.0/specification/#object-container-files&quot; target=&quot;_blank&quot;&gt;Файлы-контейнеры&lt;/a&gt; (Object Container Files): встроенная поддержка файловых контейнеров с метаданными.&lt;/li&gt;
    &lt;li id=&quot;YqVl&quot;&gt;&lt;a href=&quot;https://avro.apache.org/docs/1.12.0/specification/#required-codecs&quot; target=&quot;_blank&quot;&gt;Поддержка сжатия&lt;/a&gt; (поддерживаемые кодеки сжатия deflate, xz, snappy, zstandard, bzip2).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;gHvy&quot;&gt;Поддерживаемые типы данных&lt;/h3&gt;
  &lt;p id=&quot;3UCD&quot;&gt;Avro поддерживает следующие примитивные типы:&lt;/p&gt;
  &lt;ul id=&quot;tRgm&quot;&gt;
    &lt;li id=&quot;HO9X&quot;&gt;null&lt;/li&gt;
    &lt;li id=&quot;gw0R&quot;&gt;boolean (логический)&lt;/li&gt;
    &lt;li id=&quot;QDAE&quot;&gt;int (32-битное со знаком)&lt;/li&gt;
    &lt;li id=&quot;ffQ9&quot;&gt;long (64-битное со знаком)&lt;/li&gt;
    &lt;li id=&quot;WB70&quot;&gt;float (32-битное IEEE 754)&lt;/li&gt;
    &lt;li id=&quot;KWC1&quot;&gt;double (64-битное IEEE 754)&lt;/li&gt;
    &lt;li id=&quot;Rodb&quot;&gt;bytes (последовательность 8-битных беззнаковых байтов)&lt;/li&gt;
    &lt;li id=&quot;A6eY&quot;&gt;string (последовательность символов Unicode)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;D94P&quot;&gt;Сложные типы включают:&lt;/p&gt;
  &lt;ul id=&quot;bS7U&quot;&gt;
    &lt;li id=&quot;BPyR&quot;&gt;record (похоже на объекты/структуры)&lt;/li&gt;
    &lt;li id=&quot;Habi&quot;&gt;enum (перечисляемые значения)&lt;/li&gt;
    &lt;li id=&quot;oPRB&quot;&gt;array (упорядоченные коллекции)&lt;/li&gt;
    &lt;li id=&quot;orMG&quot;&gt;map (неупорядоченные пары ключ/значение)&lt;/li&gt;
    &lt;li id=&quot;QcaQ&quot;&gt;union&lt;/li&gt;
    &lt;li id=&quot;xIm6&quot;&gt;fixed (байтовый массив фиксированного размера)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;SmwP&quot;&gt;Более подробно прочитать про типы можно &lt;a href=&quot;https://avro.apache.org/docs/1.12.0/specification/&quot; target=&quot;_blank&quot;&gt;на странице спецификации&lt;/a&gt; &lt;/p&gt;
  &lt;h3 id=&quot;za9g&quot;&gt;Использование&lt;/h3&gt;
  &lt;p id=&quot;lwkl&quot;&gt;Avro особенно хорошо подходит для:&lt;/p&gt;
  &lt;ul id=&quot;x07U&quot;&gt;
    &lt;li id=&quot;szqZ&quot;&gt;обработки больших данных в экосистемах Hadoop,&lt;/li&gt;
    &lt;li id=&quot;ywzh&quot;&gt;платформ потоковой передачи событий (особенно с Kafka),&lt;/li&gt;
    &lt;li id=&quot;YSrc&quot;&gt;систем, требующих эволюции схемы,&lt;/li&gt;
    &lt;li id=&quot;sbxz&quot;&gt;хранения больших наборов данных с самоописательными схемами.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;44gc&quot;&gt;Schema Registry от Confluent для Apache Kafka использует Avro для обеспечения управления схемами в платформах потоковой передачи данных.&lt;/p&gt;
  &lt;h3 id=&quot;6qNz&quot;&gt;Полезные рекомендации&lt;/h3&gt;
  &lt;ul id=&quot;He8e&quot;&gt;
    &lt;li id=&quot;HQmp&quot;&gt;Следуйте правилам эволюции схемы: придерживайтесь правил совместимости Avro при развитии схем.&lt;/li&gt;
    &lt;li id=&quot;nuak&quot;&gt;Добавляйте значения по умолчанию: предоставляйте значения по умолчанию для новых полей, чтобы поддерживать обратную совместимость.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;2mWG&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;t3yW&quot;&gt;Protocol Buffers (protobuf)&lt;/h2&gt;
  &lt;h3 id=&quot;4vO5&quot;&gt;Общее описание&lt;/h3&gt;
  &lt;p id=&quot;4kjZ&quot;&gt;Protocol Buffers (protobuf) — это нейтральный к языку, нейтральный к платформе, расширяемый механизм для сериализации структурированных данных. Один из наиболее широко используемых форматов бинарной сериализации. Он определяет структуры сообщений в файлах .proto, которые компилируются в код, специфичный для языка, который обрабатывает сериализацию, десериализацию и проверку типов.&lt;/p&gt;
  &lt;h3 id=&quot;20OJ&quot;&gt;Ключевые концепции реализации&lt;/h3&gt;
  &lt;ul id=&quot;sO4n&quot;&gt;
    &lt;li id=&quot;J1gW&quot;&gt;Язык определения интерфейса: использует файлы .proto для определения структур сообщений.&lt;/li&gt;
    &lt;li id=&quot;0Ubu&quot;&gt;Генерация кода: компилирует схемы в код, специфичный для языка.&lt;/li&gt;
    &lt;li id=&quot;8xbN&quot;&gt;Бинарный формат: компактный бинарный формат для эффективной передачи.&lt;/li&gt;
    &lt;li id=&quot;dEaZ&quot;&gt;Обратная/прямая совместимость: разработан для эволюции схемы через нумерацию полей.&lt;/li&gt;
    &lt;li id=&quot;1I42&quot;&gt;Строгая типизация: обеспечивает типы данных через сгенерированный код.&lt;/li&gt;
    &lt;li id=&quot;4P0S&quot;&gt;Номера полей: каждое поле имеет уникальный номер, используемый в бинарном кодировании.&lt;/li&gt;
    &lt;li id=&quot;mU57&quot;&gt;Вложенные сообщения: поддерживает сложные вложенные структуры данных.&lt;/li&gt;
    &lt;li id=&quot;Q7jN&quot;&gt;Определения сервисов: может определять RPC-сервисы (особенно с gRPC).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;htop&quot;&gt;Поддерживаемые типы данных&lt;/h3&gt;
  &lt;p id=&quot;HNnZ&quot;&gt;Protocol Buffers поддерживает следующие &lt;a href=&quot;https://protobuf.dev/programming-guides/proto3/#scalar&quot; target=&quot;_blank&quot;&gt;скалярные типы&lt;/a&gt;:&lt;/p&gt;
  &lt;ul id=&quot;nW32&quot;&gt;
    &lt;li id=&quot;Qtxw&quot;&gt;double, float (числа с плавающей точкой)&lt;/li&gt;
    &lt;li id=&quot;2IY9&quot;&gt;int32, int64, uint32, uint64 (целые числа)&lt;/li&gt;
    &lt;li id=&quot;JayN&quot;&gt;sint32, sint64 (целые числа со знаком, кодируются более эффективно чем int32, int64)&lt;/li&gt;
    &lt;li id=&quot;F3Ld&quot;&gt;fixed32, fixed64, sfixed32, sfixed64 (целые числа фиксированной ширины)&lt;/li&gt;
    &lt;li id=&quot;SnYG&quot;&gt;bool (логическое значение)&lt;/li&gt;
    &lt;li id=&quot;hDiI&quot;&gt;string (текст в кодировке UTF-8)&lt;/li&gt;
    &lt;li id=&quot;cFV3&quot;&gt;bytes (произвольные последовательности байтов)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;HTtg&quot;&gt;Сложные типы включают:&lt;/p&gt;
  &lt;ul id=&quot;mLEm&quot;&gt;
    &lt;li id=&quot;z4Rj&quot;&gt;enum (перечисляемые значения)&lt;/li&gt;
    &lt;li id=&quot;KNru&quot;&gt;message (композитный тип)&lt;/li&gt;
    &lt;li id=&quot;pm9G&quot;&gt;repeated fields (массивы/списки)&lt;/li&gt;
    &lt;li id=&quot;nwM3&quot;&gt;map (пары ключ-значение)&lt;/li&gt;
    &lt;li id=&quot;4NqL&quot;&gt;oneof (тип объединения)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;ptG4&quot;&gt;Использование&lt;/h3&gt;
  &lt;p id=&quot;yi0u&quot;&gt;Protocol Buffers лучше всего подходит для:&lt;/p&gt;
  &lt;ul id=&quot;WZHS&quot;&gt;
    &lt;li id=&quot;VnVc&quot;&gt;коммуникации микросервисов (особенно с gRPC),&lt;/li&gt;
    &lt;li id=&quot;SaTG&quot;&gt;мобильных приложений с ограниченной пропускной способностью,&lt;/li&gt;
    &lt;li id=&quot;PKgR&quot;&gt;систем, требующих строгой типизации и валидации,&lt;/li&gt;
    &lt;li id=&quot;DpSk&quot;&gt;кросс-языковых сервисов с последовательными интерфейсами.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;AbEr&quot;&gt;Полезные рекомендации&lt;/h3&gt;
  &lt;p id=&quot;3Mmt&quot;&gt;1. Тщательно планируйте номера полей: оставляйте пробелы в нумерации полей для будущих добавлений.&lt;/p&gt;
  &lt;p id=&quot;Ew7Z&quot;&gt;2. Никогда не изменяйте номера полей: после назначения номера полей никогда не должны меняться.&lt;/p&gt;
  &lt;p id=&quot;rl1X&quot;&gt;3. Документируйте определения сообщений: добавляйте ясные комментарии к файлам .proto.&lt;/p&gt;
  &lt;p id=&quot;P4Vm&quot;&gt;4. Используйте известные типы: используйте стандартные определения для общих понятий (временные метки и т.д.).&lt;/p&gt;
  &lt;p id=&quot;u7Xu&quot;&gt;Более подробно про все рекомендации можно прочитать &lt;a href=&quot;https://protobuf.dev/best-practices/dos-donts/&quot; target=&quot;_blank&quot;&gt;на странице проекта&lt;/a&gt; &lt;/p&gt;
  &lt;p id=&quot;97MP&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;GbPp&quot;&gt;Apache Thrift&lt;/h2&gt;
  &lt;h3 id=&quot;h9bp&quot;&gt;Общее описание&lt;/h3&gt;
  &lt;p id=&quot;OmTx&quot;&gt;Apache Thrift — это фреймворк для масштабируемой разработки кросс-языковых сервисов. Thrift объединяет программный стек с механизмом генерации кода для создания сервисов, которые эффективно работают на нескольких языках программирования. Он использует свой собственный язык определения интерфейса (Interface Definition Language (IDL)) для определения типов данных и интерфейсов сервисов, которые затем компилируются в код на целевых языках.&lt;/p&gt;
  &lt;figure id=&quot;Ykbi&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXeSs_rK1kpoRtHm0nvfk41x0F0PhA-BiUjIW4XkiV_py2JKTZhRe9dYd9jWE0ugHSPD57BNNq1xAmrSthanC_FuqknEw2wvfqdUtonZ_kgySMD3HxHyDlhaqyp5n6FMyowdz6UA9w?key=Y_vr9ezK_scMDOU1LCDLMQ&quot; width=&quot;480&quot; /&gt;
    &lt;figcaption&gt;Картинка взята с &lt;a href=&quot;https://thrift.apache.org/docs/concepts.html&quot; target=&quot;_blank&quot;&gt;https://thrift.apache.org/docs/concepts.html&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;zGtZ&quot;&gt;Ключевые концепции реализации&lt;/h3&gt;
  &lt;ul id=&quot;Q8A9&quot;&gt;
    &lt;li id=&quot;y0yo&quot;&gt;&lt;a href=&quot;https://thrift.apache.org/docs/concepts.html&quot; target=&quot;_blank&quot;&gt;Многоуровневая архитектура&lt;/a&gt;: Разделяет транспорт, протокол, процессор и сервер. &lt;/li&gt;
    &lt;li id=&quot;ZRNj&quot;&gt;Идентификаторы полей: числовые идентификаторы для полей, обеспечивающие обратную совместимость.&lt;/li&gt;
    &lt;li id=&quot;6ee5&quot;&gt;Абстракция транспорта: множество вариантов транспорта (TCP, HTTP и т.д.).&lt;/li&gt;
    &lt;li id=&quot;SHoQ&quot;&gt;Абстракция протокола: множество протоколов связи (бинарный, компактный, JSON).&lt;/li&gt;
    &lt;li id=&quot;h6YE&quot;&gt;Обработка исключений: встроенная поддержка для определения и передачи исключений.&lt;/li&gt;
    &lt;li id=&quot;3BfF&quot;&gt;Поддержка версионирования: механизмы для обработки различий в версиях.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;UuuT&quot;&gt;Поддерживаемые типы данных&lt;/h3&gt;
  &lt;p id=&quot;0BXV&quot;&gt;Thrift поддерживает следующие &lt;a href=&quot;https://thrift.apache.org/docs/types.html&quot; target=&quot;_blank&quot;&gt;базовые типы&lt;/a&gt;:&lt;/p&gt;
  &lt;ul id=&quot;o07K&quot;&gt;
    &lt;li id=&quot;Ef8K&quot;&gt;bool (логическое значение)&lt;/li&gt;
    &lt;li id=&quot;oRxj&quot;&gt;byte (8-битное целое число со знаком)&lt;/li&gt;
    &lt;li id=&quot;49vu&quot;&gt;i16 (16-битное целое число со знаком)&lt;/li&gt;
    &lt;li id=&quot;dajZ&quot;&gt;i32 (32-битное целое число со знаком)&lt;/li&gt;
    &lt;li id=&quot;5HN1&quot;&gt;i64 (64-битное целое число со знаком)&lt;/li&gt;
    &lt;li id=&quot;nDUK&quot;&gt;double (64-битное число с плавающей точкой)&lt;/li&gt;
    &lt;li id=&quot;Sxhc&quot;&gt;string (текст, кодированный в UTF-8)&lt;/li&gt;
    &lt;li id=&quot;fauK&quot;&gt;binary (некодированные последовательности байтов)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;fZOu&quot;&gt;&lt;a href=&quot;https://thrift.apache.org/docs/types.html#containers&quot; target=&quot;_blank&quot;&gt;Контейнерные типы&lt;/a&gt;:&lt;/p&gt;
  &lt;ul id=&quot;m3LA&quot;&gt;
    &lt;li id=&quot;lv6l&quot;&gt;list (упорядоченный список элементов)&lt;/li&gt;
    &lt;li id=&quot;8XSR&quot;&gt;set (неупорядоченное множество уникальных элементов)&lt;/li&gt;
    &lt;li id=&quot;mL7S&quot;&gt;map (пары ключ-значение)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;cSDf&quot;&gt;&lt;a href=&quot;https://thrift.apache.org/docs/idl#thrift-interface-description-language&quot; target=&quot;_blank&quot;&gt;Пользовательские типы&lt;/a&gt;:&lt;/p&gt;
  &lt;ul id=&quot;2fIW&quot;&gt;
    &lt;li id=&quot;VBTF&quot;&gt;struct (похоже на классы или записи)&lt;/li&gt;
    &lt;li id=&quot;8Sq6&quot;&gt;enum (перечисляемые значения)&lt;/li&gt;
    &lt;li id=&quot;qrSj&quot;&gt;union (подобно C-объединениям)&lt;/li&gt;
    &lt;li id=&quot;GV1Z&quot;&gt;exception (для обработки ошибок)&lt;/li&gt;
    &lt;li id=&quot;3AQC&quot;&gt;typedef (псевдонимы типов)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;kfX5&quot;&gt;Использование&lt;/h3&gt;
  &lt;ul id=&quot;8I8y&quot;&gt;
    &lt;li id=&quot;Namf&quot;&gt;построение архитектур микросервисов с использование различных языков,&lt;/li&gt;
    &lt;li id=&quot;8NOR&quot;&gt;кросс-языковые RPC-системы,&lt;/li&gt;
    &lt;li id=&quot;MvGI&quot;&gt;высокопроизводительные интерфейсы сервисов,&lt;/li&gt;
    &lt;li id=&quot;xTi7&quot;&gt;системы, требующие как определения данных, так и интерфейсов сервисов.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;C8mx&quot;&gt;Полезные рекомендации&lt;/h3&gt;
  &lt;p id=&quot;WX2t&quot;&gt;1. Тщательно проектируйте сервисы: инвестируйте время в продуманное проектирование интерфейса.&lt;/p&gt;
  &lt;p id=&quot;4gV6&quot;&gt;2. Используйте подходящие идентификаторы полей: назначайте идентификаторы полей с запасом для будущего расширения.&lt;/p&gt;
  &lt;p id=&quot;yeYO&quot;&gt;3. Выбирайте правильный протокол: выбирайте между бинарным, компактным или JSON в зависимости от потребностей.&lt;/p&gt;
  &lt;p id=&quot;zLZ5&quot;&gt;Придерживайтесь стандарта, &lt;a href=&quot;https://thrift.apache.org/docs/coding_standards.html&quot; target=&quot;_blank&quot;&gt;рекомендованного&lt;/a&gt; при использовании Thrift. &lt;/p&gt;
  &lt;p id=&quot;EtCS&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;hqJy&quot;&gt;Apache Parquet&lt;/h2&gt;
  &lt;h3 id=&quot;Hc3I&quot;&gt;Общее описание&lt;/h3&gt;
  &lt;p id=&quot;M5F6&quot;&gt;Apache Parquet — это формат столбцового хранения, разработанный для эффективной обработки и хранения данных. Parquet оптимизирован для работы со сложными структурами данных в больших объемах. В отличие от других обсуждаемых нами протоколов, которые в основном предназначены для сериализации сообщений, Parquet специально создан для аналитических рабочих нагрузок, где распространено чтение только определенных столбцов данных. Apache Parquet получил самое широкое распространение в большом количестве популярных фреймворков (&lt;a href=&quot;https://iceberg.apache.org/spec/#parquet&quot; target=&quot;_blank&quot;&gt;apache iceberg&lt;/a&gt;, &lt;a href=&quot;https://hudi.apache.org/blog/2024/07/31/hudi-file-formats/&quot; target=&quot;_blank&quot;&gt;apache hudi&lt;/a&gt;), библиотеках (&lt;a href=&quot;https://arrow.apache.org/docs/python/parquet.html&quot; target=&quot;_blank&quot;&gt;apache arrow&lt;/a&gt;) и аналитических БД (&lt;a href=&quot;https://duckdb.org/docs/stable/data/parquet/overview.html&quot; target=&quot;_blank&quot;&gt;duckdb&lt;/a&gt;, &lt;a href=&quot;https://clickhouse.com/docs/integrations/data-formats/parquet&quot; target=&quot;_blank&quot;&gt;clickhouse&lt;/a&gt;).&lt;/p&gt;
  &lt;figure id=&quot;LQjM&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXfPDIdgYan8e21eqJbi6pVoqgPxAJQsTjZLYqFXKCm16m9J7VsEUFKf0lxEDDAGYYL_jATp8UFN93SClDv-yvnVTUmVVoTJRFJziQ8gHCXbBR-Mw1BTtBRFSzUiYDPlWjer3QeI?key=Y_vr9ezK_scMDOU1LCDLMQ&quot; width=&quot;601&quot; /&gt;
    &lt;figcaption&gt;Картинка взята с &lt;a href=&quot;https://parquet.apache.org/docs/file-format/&quot; target=&quot;_blank&quot;&gt;https://parquet.apache.org/docs/file-format/&lt;/a&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;05l2&quot;&gt;Ключевые концепции реализации&lt;/h3&gt;
  &lt;ul id=&quot;bseu&quot;&gt;
    &lt;li id=&quot;8BWv&quot;&gt;Столбцовый формат: хранит данные по столбцам, а не по строкам.&lt;/li&gt;
    &lt;li id=&quot;mH4X&quot;&gt;Сжатие: поддерживает несколько алгоритмов сжатия для каждого столбца.&lt;/li&gt;
    &lt;li id=&quot;7tq8&quot;&gt;Организация страниц и групп строк: данные организованы в страницы и группы строк.&lt;/li&gt;
    &lt;li id=&quot;PEv9&quot;&gt;Эволюция схемы: позволяет добавлять, удалять или изменять столбцы.&lt;/li&gt;
    &lt;li id=&quot;8ULV&quot;&gt;Кодирование, специфичное для типа: использует специализированные кодировки для разных типов данных.&lt;/li&gt;
    &lt;li id=&quot;4W0t&quot;&gt;Поддержка вложенных данных: эффективно обрабатывает сложные вложенные структуры.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;QxSl&quot;&gt;Поддерживаемые типы данных&lt;/h3&gt;
  &lt;p id=&quot;b0bK&quot;&gt;Parquet поддерживает следующие примитивные типы:&lt;/p&gt;
  &lt;ul id=&quot;C6Yd&quot;&gt;
    &lt;li id=&quot;Xfbv&quot;&gt;boolean (логический)&lt;/li&gt;
    &lt;li id=&quot;lUe3&quot;&gt;int32/int64 (целые)&lt;/li&gt;
    &lt;li id=&quot;0QW9&quot;&gt;int96 (для временных меток)&lt;/li&gt;
    &lt;li id=&quot;uCh4&quot;&gt;float/double (с плавающей точкой)&lt;/li&gt;
    &lt;li id=&quot;ZtJm&quot;&gt;byte array (для строк и бинарных данных)&lt;/li&gt;
    &lt;li id=&quot;FykA&quot;&gt;fixed length byte array (байтовый массив фиксированной длины)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;89V1&quot;&gt;Использование&lt;/h3&gt;
  &lt;p id=&quot;lhtx&quot;&gt;Parquet полезен в следующих случаях:&lt;/p&gt;
  &lt;ul id=&quot;ZtI9&quot;&gt;
    &lt;li id=&quot;PXSr&quot;&gt;хранилищах данных и аналитике,&lt;/li&gt;
    &lt;li id=&quot;Pw7B&quot;&gt;обработке больших данных с Hadoop/Spark,&lt;/li&gt;
    &lt;li id=&quot;KaE1&quot;&gt;хранении больших наборов данных для пакетной обработки,&lt;/li&gt;
    &lt;li id=&quot;5xpO&quot;&gt;системах с колоночно-ориентированными шаблонами запросов,&lt;/li&gt;
    &lt;li id=&quot;0nkC&quot;&gt;озерах данных и холодном хранилище.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Rrd9&quot;&gt;Кроме того, стоит отметить, что в сообществе Prometheus идет &lt;a href=&quot;https://docs.google.com/document/d/1dutHwZXibnq3_gIeMLMZh9uFkWToQ2TwazdoqFJzlEs/edit?tab=t.0#heading=h.6tqrdajo7je9&quot; target=&quot;_blank&quot;&gt;активное обсуждение&lt;/a&gt; внедрения поддержки формата Parquet.&lt;/p&gt;
  &lt;p id=&quot;pdDg&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;Dyb2&quot;&gt;Заключение&lt;/h2&gt;
  &lt;h3 id=&quot;Ucfd&quot;&gt;MessagePack&lt;/h3&gt;
  &lt;p id=&quot;uLH3&quot;&gt;Лучше всего подходит для простой и эффективной замены JSON, где важна гибкость схемы.&lt;/p&gt;
  &lt;ul id=&quot;ESKI&quot;&gt;
    &lt;li id=&quot;NyKV&quot;&gt;Преимущества: не требуется схема, компактный бинарный формат.&lt;/li&gt;
    &lt;li id=&quot;2bTc&quot;&gt;Ограничения: нет эволюции схемы, ограниченная безопасность типов.&lt;/li&gt;
    &lt;li id=&quot;SVuO&quot;&gt;Сценарии использования: мобильные приложения, простые API, замена JSON для повышения производительности.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;oHx9&quot;&gt;Apache Avro&lt;/h3&gt;
  &lt;p id=&quot;tFRV&quot;&gt;Лучше всего подходит для систем, требующих эволюции схемы.&lt;/p&gt;
  &lt;ul id=&quot;1DfG&quot;&gt;
    &lt;li id=&quot;m2Sp&quot;&gt;Преимущества: отличная эволюция схемы, самоописание, компактный формат.&lt;/li&gt;
    &lt;li id=&quot;YQaK&quot;&gt;Ограничения: требует определения схемы, меньше инструментария для некоторых языков.&lt;/li&gt;
    &lt;li id=&quot;5d4l&quot;&gt;Сценарии использования: экосистема Hadoop, развивающиеся конвейеры данных, интеграции с Kafka.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;7MAE&quot;&gt;Protocol Buffers&lt;/h3&gt;
  &lt;p id=&quot;rQOy&quot;&gt;Лучше всего подходит для строго типизированных интерфейсов сервисов с кросс-языковой совместимостью.&lt;/p&gt;
  &lt;ul id=&quot;QPLT&quot;&gt;
    &lt;li id=&quot;OIsG&quot;&gt;Преимущества: отличный инструментарий, сильная безопасность типов, эффективное кодирование.&lt;/li&gt;
    &lt;li id=&quot;yxgY&quot;&gt;Ограничения: меньшая гибкость схемы, чем у Avro, нет встроенного RPC (нужен gRPC).&lt;/li&gt;
    &lt;li id=&quot;BqCG&quot;&gt;Сценарии использования: микросервисы, определения и описания API, мобильные бэкенды.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;8eR9&quot;&gt;Apache Thrift&lt;/h3&gt;
  &lt;p id=&quot;3Abn&quot;&gt;Лучше всего подходит для полных RPC-фреймворков, охватывающих несколько языков программирования.&lt;/p&gt;
  &lt;ul id=&quot;gLGr&quot;&gt;
    &lt;li id=&quot;HAiA&quot;&gt;Преимущества: встроенная поддержка RPC, несколько протоколов, комплексное решение.&lt;/li&gt;
    &lt;li id=&quot;JhYD&quot;&gt;Ограничения: более сложный, чем другие протоколы.&lt;/li&gt;
    &lt;li id=&quot;aumJ&quot;&gt;Идеальные сценарии: мультиязычные сервисные архитектуры, кросс-языковые RPC.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;dfzr&quot;&gt;Apache Parquet&lt;/h3&gt;
  &lt;p id=&quot;JvI6&quot;&gt;Лучше всего подходит для аналитических рабочих нагрузок с колоночно-ориентированными шаблонами доступа.&lt;/p&gt;
  &lt;ul id=&quot;Kmbc&quot;&gt;
    &lt;li id=&quot;TLAk&quot;&gt;Преимущества: столбцовое хранение, отличное сжатие.&lt;/li&gt;
    &lt;li id=&quot;VoLh&quot;&gt;Ограничения: не подходит для доступа к отдельным записям, аналитический фокус.&lt;/li&gt;
    &lt;li id=&quot;xmme&quot;&gt;Сценарии использования: хранилища данных, обработка в Spark/Hadoop, озера данных.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;al2y&quot;&gt;При выборе учитывайте:&lt;/p&gt;
  &lt;p id=&quot;JRkM&quot;&gt;1. Шаблоны доступа: колоночные или строчные.&lt;/p&gt;
  &lt;p id=&quot;pHhR&quot;&gt;2. Потребности в эволюции: как часто ваши структуры данных будут меняться.&lt;/p&gt;
  &lt;p id=&quot;xLma&quot;&gt;3. Языковые требования: какие языки программирования вам необходимо поддерживать.&lt;/p&gt;
  &lt;p id=&quot;yPZJ&quot;&gt;4. Безопасность типов: насколько важна строгая типизация для вашего случая использования.&lt;/p&gt;
  &lt;p id=&quot;Hy1A&quot;&gt;Говоря про Parquet, мы не рассмотрели такой формат для аналитики, как ORC (&lt;a href=&quot;https://orc.apache.org/&quot; target=&quot;_blank&quot;&gt;Optimized Row Columnar&lt;/a&gt;), но он используется по большей части в экосистеме Hadoop и является более узкоспециализированным.&lt;/p&gt;
  &lt;h3 id=&quot;WowR&quot;&gt;Дополнительные материалы&lt;/h3&gt;
  &lt;ul id=&quot;UYVJ&quot;&gt;
    &lt;li id=&quot;jFuX&quot;&gt;Обсуждение использования Parquet в Prometheus - &lt;a href=&quot;https://docs.google.com/document/d/1dutHwZXibnq3_gIeMLMZh9uFkWToQ2TwazdoqFJzlEs/edit?tab=t.0#heading=h.6tqrdajo7je9&quot; target=&quot;_blank&quot;&gt;https://docs.google.com/document/d/1dutHwZXibnq3_gIeMLMZh9uFkWToQ2TwazdoqFJzlEs/edit?tab=t.0#heading=h.6tqrdajo7je9&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;oMs4&quot;&gt;Apache Thrift - &lt;a href=&quot;https://thrift.apache.org/docs/&quot; target=&quot;_blank&quot;&gt;https://thrift.apache.org/docs/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;wezR&quot;&gt;Apache Avro - &lt;a href=&quot;https://avro.apache.org/docs/&quot; target=&quot;_blank&quot;&gt;https://avro.apache.org/docs/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;bde6&quot;&gt;Protobuf - &lt;a href=&quot;https://protobuf.dev/&quot; target=&quot;_blank&quot;&gt;https://protobuf.dev/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;jm1o&quot;&gt;Apache Parquet - &lt;a href=&quot;https://parquet.apache.org/docs/&quot; target=&quot;_blank&quot;&gt;https://parquet.apache.org/docs/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;bkEz&quot;&gt;MessagePack - &lt;a href=&quot;https://msgpack.org/&quot; target=&quot;_blank&quot;&gt;https://msgpack.org/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;zCh4&quot;&gt;Статья про форматы файлов и данных - &lt;a href=&quot;https://habr.com/ru/companies/vk/articles/741702/&quot; target=&quot;_blank&quot;&gt;https://habr.com/ru/companies/vk/articles/741702/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;HsLD&quot;&gt;Книга - Designing Data-Intensive Applications by Martin Kleppmann - &lt;a href=&quot;https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/&quot; target=&quot;_blank&quot;&gt;https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/&lt;/a&gt; (в декабре 2025 года планируется выход 2 издания этой книги &lt;a href=&quot;https://www.oreilly.com/library/view/designing-data-intensive-applications/9781098119058/&quot; target=&quot;_blank&quot;&gt;https://www.oreilly.com/library/view/designing-data-intensive-applications/9781098119058/&lt;/a&gt;)&lt;/li&gt;
  &lt;/ul&gt;

</content></entry><entry><id>rnds:066-configuration-languages</id><link rel="alternate" type="text/html" href="https://blog.rnds.pro/066-configuration-languages?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rnds"></link><title>Конфигурационный полиглот или обзор языков конфигураций</title><published>2025-05-08T20:08:47.224Z</published><updated>2025-05-13T17:16:13.490Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/6a/46/6a46f018-8754-42ed-9a77-8ba7a99cd1f1.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/04/63/0463512d-006c-48b5-849d-73c754ed79a7.jpeg&quot;&gt;Конфигурационный Вавилон наших дней:                                                                        KCL, KDL, Jsonnet и Cue lang — словно ручейки многоязычной реки Финнегана,               текущие сквозь цифровую Лиффи современности.                                                Riverrun, через конфиги и абстракции,                                                                                                от схем к значениям мы приходим...

Джеймс Джойс о языках конфигураций</summary><content type="html">
  &lt;blockquote id=&quot;w5SW&quot; data-align=&quot;right&quot;&gt;&lt;em&gt;Конфигурационный Вавилон наших дней:                                                                        KCL, KDL, Jsonnet и Cue lang — словно ручейки многоязычной реки Финнегана,               текущие сквозь цифровую Лиффи современности.                                                Riverrun, через конфиги и абстракции,                                                                                                от схем к значениям мы приходим...&lt;br /&gt;&lt;br /&gt;Джеймс Джойс о языках конфигураций&lt;/em&gt;&lt;/blockquote&gt;
  &lt;figure id=&quot;cIMR&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/04/63/0463512d-006c-48b5-849d-73c754ed79a7.jpeg&quot; width=&quot;1043&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;qi8h&quot;&gt;Введение&lt;/p&gt;
  &lt;p id=&quot;nEkq&quot;&gt;В данной статье мы рассмотрим не такие популярные языки конфигураций, как YAML, JSON, XML, INI, HCL, но более экзотические, но и не такие маргинальные. В данной статье рассмотрим &lt;a href=&quot;https://cuelang.org/&quot; target=&quot;_blank&quot;&gt;cue-lang&lt;/a&gt;, &lt;a href=&quot;https://dhall-lang.org/&quot; target=&quot;_blank&quot;&gt;dhall&lt;/a&gt;, &lt;a href=&quot;https://kdl.dev/&quot; target=&quot;_blank&quot;&gt;kdl&lt;/a&gt;, &lt;a href=&quot;https://jsonnet.org/&quot; target=&quot;_blank&quot;&gt;jsonnet&lt;/a&gt;, &lt;a href=&quot;https://www.kcl-lang.io/&quot; target=&quot;_blank&quot;&gt;KCL&lt;/a&gt;. Будем смотреть от простого к сложному.&lt;/p&gt;
  &lt;p id=&quot;VjfE&quot;&gt;Основные ограничения таких языков, как YAML, JSON, XML, INI, HCL:&lt;/p&gt;
  &lt;ul id=&quot;rgfJ&quot;&gt;
    &lt;li id=&quot;zZpa&quot;&gt;отсутствие проверки типов,&lt;/li&gt;
    &lt;li id=&quot;vpX4&quot;&gt;отсутствие логических проверок,&lt;/li&gt;
    &lt;li id=&quot;Am24&quot;&gt;отсутствие поддержки абстракций и ограниченных возможностей повторного использования (в HCL есть базовые возможности через блоки и переменные; в YAML - якоря, но имеют ограничения использования в рамках одного файла, а также затрудняют чтение при сложных вложенных структурах),&lt;/li&gt;
    &lt;li id=&quot;QZUp&quot;&gt;отсутствующие или ограниченные возможности программирования,&lt;/li&gt;
    &lt;li id=&quot;Xrir&quot;&gt;склонность к синтаксическим ошибкам (отступы в YAML, скобочки и запятые в JSON),&lt;/li&gt;
    &lt;li id=&quot;dz4H&quot;&gt;проверка осуществляется внешними инструментами (Json schema, XML schema),&lt;/li&gt;
    &lt;li id=&quot;i470&quot;&gt;проверка происходит постфактум, после создания конфигурации.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;gvkA&quot;&gt;Эти ограничения становятся особенно проблематичными по мере роста сложности и масштаба конфигураций инфраструктуры и приложений.&lt;/p&gt;
  &lt;h3 id=&quot;u76r&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;uboK&quot;&gt;KDL&lt;/h3&gt;
  &lt;p id=&quot;h0Of&quot;&gt;Этот претендент сильно выбивается из нашего short list конфигурационных монстров, но должны же мы порадовать тех, кому хочется выбрать хороший язык конфигурации для своего pet проекта.&lt;/p&gt;
  &lt;p id=&quot;l7Pg&quot;&gt;Основные характеристики KDL:&lt;/p&gt;
  &lt;ul id=&quot;IEfn&quot;&gt;
    &lt;li id=&quot;lBb3&quot;&gt;Структура на основе узлов: все в KDL — это узел с именем, необязательными свойствами и необязательными дочерними элементами.&lt;/li&gt;
    &lt;li id=&quot;nryQ&quot;&gt;&lt;a href=&quot;https://github.com/kdl-org/kdl/tree/main?tab=readme-ov-file#comments&quot; target=&quot;_blank&quot;&gt;Типы данных&lt;/a&gt;: строки, числа, логические значения (boolean) и null.&lt;/li&gt;
    &lt;li id=&quot;Bg20&quot;&gt;Поддерживает многострочные выражения.&lt;/li&gt;
    &lt;li id=&quot;uGTF&quot;&gt;&lt;a href=&quot;https://github.com/kdl-org/kdl/tree/main?tab=readme-ov-file#comments&quot; target=&quot;_blank&quot;&gt;Комментарии&lt;/a&gt;: поддерживает как строчные, так и блочные комментарии.&lt;/li&gt;
    &lt;li id=&quot;y54j&quot;&gt;&lt;a href=&quot;https://github.com/kdl-org/kdl/tree/main?tab=readme-ov-file#type-annotations&quot; target=&quot;_blank&quot;&gt;Аннотации&lt;/a&gt;: предоставляет метаданные об узлах с использованием синтаксиса (аннотаций).&lt;/li&gt;
    &lt;li id=&quot;liFR&quot;&gt;Поддержка Unicode: полная поддержка Unicode, включая идентификаторы.&lt;/li&gt;
    &lt;li id=&quot;Uhbf&quot;&gt;&lt;a href=&quot;https://github.com/kdl-org/kdl?tab=readme-ov-file#implementations&quot; target=&quot;_blank&quot;&gt;Библиотеки&lt;/a&gt;, есть почти для всех языков программирования&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;SnPy&quot;&gt;Примеры использования KDL:&lt;/p&gt;
  &lt;p id=&quot;3ant&quot;&gt;1. Конфигурация приложения&lt;/p&gt;
  &lt;p id=&quot;PcKF&quot;&gt;KDL отлично подходит для определения настроек приложения, благодаря своей интуитивно понятной структуре:&lt;/p&gt;
  &lt;pre id=&quot;CNTr&quot;&gt;config {
  server {
    port 8080
    host &amp;quot;0.0.0.0&amp;quot;
    timeout 30s
  }
  database {
    url &amp;quot;postgres://localhost:5432/myapp&amp;quot;
    max-connections 100
    (sensitive) password &amp;quot;my-password&amp;quot;
  }
}&lt;/pre&gt;
  &lt;p id=&quot;RGYl&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;9vhY&quot;&gt;2. Сериализация данных&lt;/p&gt;
  &lt;p id=&quot;xWll&quot;&gt;KDL можно использовать для обмена данными, аналогично JSON, но с дополнительной выразительностью&lt;/p&gt;
  &lt;pre id=&quot;O8TL&quot;&gt;users {
  user {
    id 1
    name &amp;quot;Alice&amp;quot;
    roles [&amp;quot;admin&amp;quot;, &amp;quot;user&amp;quot;]
  }
  user {
    id 2
    name &amp;quot;Bob&amp;quot;
    roles [&amp;quot;user&amp;quot;]
  }
}&lt;/pre&gt;
  &lt;p id=&quot;RaBJ&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Dtlg&quot;&gt;Есть руководства по тому, как &lt;a href=&quot;https://github.com/kdl-org/kdl/blob/main/JSON-IN-KDL.md&quot; target=&quot;_blank&quot;&gt;JSON конвертировать в KDL&lt;/a&gt;,&lt;/p&gt;
  &lt;p id=&quot;91Qk&quot;&gt;а также &lt;a href=&quot;https://github.com/kdl-org/kdl/blob/main/JSON-IN-KDL.md&quot; target=&quot;_blank&quot;&gt;XML в KDL&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;cwyf&quot;&gt;Спецификация языка описана &lt;a href=&quot;https://github.com/kdl-org/kdl/blob/main/draft-marchan-kdl2.md&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Yabd&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;X8Lh&quot;&gt;JSONNET&lt;/h3&gt;
  &lt;p id=&quot;Kqs7&quot;&gt;Является языком шаблонизации, который расширяет JSON такими фичами, как переменные, функции и условия.&lt;/p&gt;
  &lt;pre id=&quot;Ily6&quot;&gt;// Простой пример
{
  person: {
    name: &amp;quot;Alice&amp;quot;,
    greeting: &amp;quot;Hello &amp;quot; + self.name + &amp;quot;!&amp;quot;,
  },

  // Используем локальные переменные
  local tax = 0.07,
  prices: {
    item: 100,
    withTax: self.item * (1 + tax),
  },
}&lt;/pre&gt;
  &lt;p id=&quot;jh1a&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;JUEP&quot;&gt;Основные сферы применения:&lt;/p&gt;
  &lt;ol id=&quot;0liF&quot;&gt;
    &lt;li id=&quot;85HY&quot;&gt;Управление ресурсами Kubernetes.&lt;/li&gt;
    &lt;li id=&quot;dj6B&quot;&gt;В утилитах из категории IaC, в т.ч. Terraform, Pulumi.&lt;/li&gt;
    &lt;li id=&quot;tjJt&quot;&gt;Конфигурирование приложений, централизованное управление конфигурацией.&lt;/li&gt;
    &lt;li id=&quot;xNRN&quot;&gt;CI/CD пайплайны: шаблонизация конфигурационных файлов (Gitlab CI, Github Actions), стандартизация пайплайнов между проектами.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;GSCZ&quot;&gt;Например, такие проекты как &lt;a href=&quot;https://tanka.dev/&quot; target=&quot;_blank&quot;&gt;Tanka&lt;/a&gt;, &lt;a href=&quot;https://qbec.io/&quot; target=&quot;_blank&quot;&gt;Qbec&lt;/a&gt; используют jsonnet для конфигурации Kubernetes.&lt;/p&gt;
  &lt;p id=&quot;AiZd&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;aEtp&quot;&gt;Dhall&lt;/h3&gt;
  &lt;p id=&quot;QPUe&quot;&gt;Основные свойства языка Dhall: он полностью функциональный язык конфигураций, а также в нем сделан упор на безопасность.&lt;/p&gt;
  &lt;p id=&quot;Q5Yi&quot;&gt;Основные характеристики Dhall:&lt;/p&gt;
  &lt;ul id=&quot;wu7Y&quot;&gt;
    &lt;li id=&quot;iWe3&quot;&gt;Строгая система типов: Dhall статически типизирован, перехватывает ошибки во время компиляции, а не во время выполнения.&lt;/li&gt;
    &lt;li id=&quot;J8oX&quot;&gt;Полные функции: все функции завершаются, предотвращая бесконечные циклы.&lt;/li&gt;
    &lt;li id=&quot;6Jtb&quot;&gt;Dhall &lt;a href=&quot;https://docs.dhall-lang.org/discussions/Safety-guarantees.html#turing-completeness&quot; target=&quot;_blank&quot;&gt;неполный по Тьюрингу&lt;/a&gt; язык, что по замыслу позволяет избегать проблем полных по Тьюрингу языков (проверка типов за конечное время, отсутствие рекурсии).&lt;/li&gt;
    &lt;li id=&quot;Fefu&quot;&gt;Нормализация: выражения можно &lt;a href=&quot;https://github.com/dhall-lang/dhall-lang/tree/master/standard#semantics&quot; target=&quot;_blank&quot;&gt;нормализовать&lt;/a&gt; до стандартной формы, что упрощает сравнение конфигураций.&lt;/li&gt;
    &lt;li id=&quot;ZSBD&quot;&gt;Безопасные импорты пакетов с проверкой целостности.&lt;/li&gt;
    &lt;li id=&quot;dVm8&quot;&gt;&lt;a href=&quot;https://docs.dhall-lang.org/howtos/How-to-integrate-Dhall.html#language-support&quot; target=&quot;_blank&quot;&gt;Биндинги&lt;/a&gt; для различных языков.&lt;/li&gt;
    &lt;li id=&quot;Qwzv&quot;&gt;Есть различные &lt;a href=&quot;https://store.dhall-lang.org/&quot; target=&quot;_blank&quot;&gt;пакеты&lt;/a&gt; для поддержки конфигурации Dhall в популярных инструментах ansible, kubernetes.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;enLU&quot;&gt;Пример:&lt;/p&gt;
  &lt;pre id=&quot;9I8a&quot;&gt;let Config : Type =
  {- What happens if you add another field here? -}
  { home : Text
  , privateKey : Text
  , publicKey : Text
  }

let makeUser : Text -&amp;gt; Config = \(user : Text) -&amp;gt;
  let home       : Text   = &amp;quot;/home/${user}&amp;quot;
  let privateKey : Text   = &amp;quot;${home}/.ssh/id_ed25519&amp;quot;
  let publicKey  : Text   = &amp;quot;${privateKey}.pub&amp;quot;
  let config     : Config = { home, privateKey, publicKey }
  in  config

let configs : List Config =
  [ makeUser &amp;quot;bill&amp;quot;
  , makeUser &amp;quot;jane&amp;quot;
  ]
in  configs&lt;/pre&gt;
  &lt;p id=&quot;1xab&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;09tU&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;YSqb&quot;&gt;KCL&lt;/h3&gt;
  &lt;p id=&quot;9k3c&quot;&gt;KCL -  это язык конфигураций на основе ограничений с возможностями проверки, модульности и применения политик.&lt;/p&gt;
  &lt;p id=&quot;azoL&quot;&gt;Основные характеристики KCL:&lt;/p&gt;
  &lt;ul id=&quot;mZvr&quot;&gt;
    &lt;li id=&quot;g4QX&quot;&gt;Строгая статическая типизация с выводом типов, перехватом ошибок до времени выполнения.&lt;/li&gt;
    &lt;li id=&quot;bpSJ&quot;&gt;Проверка на основе схемы для определения структурированных конфигураций с правилами проверки.&lt;/li&gt;
    &lt;li id=&quot;wiu2&quot;&gt;Политика как код для определения и обеспечения соблюдения организационных стандартов.&lt;/li&gt;
    &lt;li id=&quot;W4Kb&quot;&gt;Неизменяемость (иммутабельность) - переменные по умолчанию неизменяемы, что способствует более безопасным конфигурациям.&lt;/li&gt;
    &lt;li id=&quot;KVkr&quot;&gt;Богатая стандартная библиотека со встроенными функциями.&lt;/li&gt;
    &lt;li id=&quot;HO2b&quot;&gt;Система импорта, обеспечивающая модульную конфигурацию с пакетами.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;WQpX&quot;&gt;Хорошо интегрируется с большим количеством DevOps инструментов, в т.ч. Kubernetes (есть специальная спецификация &lt;a href=&quot;https://github.com/kcl-lang/krm-kcl&quot; target=&quot;_blank&quot;&gt;https://github.com/kcl-lang/krm-kcl&lt;/a&gt;), Terraform, CI/CD пайплайны, GitOps (ArgoCD, FluxCD).&lt;/p&gt;
  &lt;p id=&quot;xWcw&quot;&gt;Расширяемость за счет написания собственных &lt;a href=&quot;https://www.kcl-lang.io/docs/reference/plugin/overview&quot; target=&quot;_blank&quot;&gt;плагинов&lt;/a&gt; &lt;/p&gt;
  &lt;p id=&quot;AAgA&quot;&gt;Поддержка больше &lt;a href=&quot;https://www.kcl-lang.io/docs/reference/xlang-api/overview&quot; target=&quot;_blank&quot;&gt;10 SDK для разных языков&lt;/a&gt; &lt;/p&gt;
  &lt;p id=&quot;GAbi&quot;&gt;Библиотека с несколькими сотнями &lt;a href=&quot;https://artifacthub.io/packages/search?org=kcl&amp;sort=relevance&amp;page=1&quot; target=&quot;_blank&quot;&gt;модулей&lt;/a&gt; &lt;/p&gt;
  &lt;p id=&quot;TwYg&quot;&gt;Большая &lt;a href=&quot;https://github.com/kcl-lang/examples&quot; target=&quot;_blank&quot;&gt;библиотека примеров&lt;/a&gt; для различных случаев использования &lt;/p&gt;
  &lt;figure id=&quot;eo6U&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXcNFTGwoE5Wwg2iahk4Cd6KUEg3ZkHZewuvbhdT0hV1-K8H5rn1t2QkN-n0iwBqZiwvmT5uBECMIuTI6qw6wyVt25wbY_DYmdemQm92p0COx1JPIijqzpIvWmGQN-mHr_a7PYKg?key=49mMlFvNQL8qwRvW6iZZMZDM&quot; width=&quot;594&quot; /&gt;
    &lt;figcaption&gt;Картинка взята с https://www.kcl-lang.io/&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;tKZq&quot;&gt;Пример простого конфига&lt;/p&gt;
  &lt;p id=&quot;TZIf&quot;&gt;server.k&lt;/p&gt;
  &lt;pre id=&quot;iMwU&quot;&gt;title = &amp;quot;KCL Example&amp;quot;
owner = {
  name = &amp;quot;The KCL Authors&amp;quot;
  data = &amp;quot;2020-01-02T03:04:05&amp;quot;
}
database = {
  enabled = True
  ports = [8000, 8001, 8002]
  data = [[&amp;quot;delta&amp;quot;, &amp;quot;phi&amp;quot;], [3.14]]
  temp_targets = {cpu = 79.5, case = 72.0}
}

servers = [
  {ip = &amp;quot;10.0.0.1&amp;quot;, role = &amp;quot;frontend&amp;quot;}
  {ip = &amp;quot;10.0.0.2&amp;quot;, role = &amp;quot;backend&amp;quot;}
]&lt;/pre&gt;
  &lt;p id=&quot;X1fY&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;zIk9&quot;&gt;В результате выполнения&lt;/p&gt;
  &lt;pre id=&quot;sByt&quot;&gt;kcl server.k&lt;/pre&gt;
  &lt;p id=&quot;UeVZ&quot;&gt;получим следующий YAML файл&lt;/p&gt;
  &lt;pre id=&quot;AWyH&quot;&gt;title: KCL Example
owner:
  name: The KCL Authors
  data: &amp;quot;2020-01-02T03:04:05&amp;quot;
database:
  enabled: true
  ports:
    - 8000
    - 8001
    - 8002
  data:
    - - delta
      - phi
    - - 3.14
temp_targets:
  cpu: 79.5
  case: 72.0
servers:
  - ip: 10.0.0.1
  role: frontend
  - ip: 10.0.0.2
  role: backend&lt;/pre&gt;
  &lt;p id=&quot;rW93&quot;&gt;- ip: 10.0.0.2&lt;/p&gt;
  &lt;p id=&quot;r9om&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;nU75&quot;&gt;Пример описания схемы&lt;/p&gt;
  &lt;pre id=&quot;RyFb&quot;&gt;schema DatabaseConfig:
  enabled: bool = True
  ports: [int] = [8000, 8001, 8002]
  data: [[str|float]] = [[&amp;quot;delta&amp;quot;, &amp;quot;phi&amp;quot;], [3.14]]
  temp_targets: {str: float} = {cpu = 79.5, case = 72.0}&lt;/pre&gt;
  &lt;p id=&quot;bwzz&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Pjiq&quot;&gt;CUE LANG&lt;/h3&gt;
  &lt;p id=&quot;o9LN&quot;&gt;Cue lang - это язык проверки данных с механизмом вывода, основанным на логическом программировании. Ключевая вещь, которая отличает Cue от других языков то, что Cue объединяет типы и значения в единую концепцию.&lt;/p&gt;
  &lt;p id=&quot;jcSU&quot;&gt;Основные характеристики Cue lang:&lt;/p&gt;
  &lt;ul id=&quot;nhZ4&quot;&gt;
    &lt;li id=&quot;FbmG&quot;&gt;Унификация схем и данных, CUE рассматривает схемы и данные как одно и тоже, что позволяет объединить их посредством унификации.&lt;/li&gt;
    &lt;li id=&quot;yfDY&quot;&gt;Декларативная природа языка и идемпотентность: повторение ограничений не меняет результат.&lt;/li&gt;
    &lt;li id=&quot;5QOh&quot;&gt;Отделение вычислений от конфигурации: данные, которые необходимо вычислить, могут быть вычислены отдельно и помещены в файл.&lt;/li&gt;
    &lt;li id=&quot;PQ7x&quot;&gt;Ограничения (constraints) CUE действуют как валидаторы данных, а также как механизм для сокращения шаблонного кода.&lt;/li&gt;
    &lt;li id=&quot;U4lk&quot;&gt;Система типов на основе &lt;a href=&quot;https://cuelang.org/docs/concept/the-logic-of-cue/#the-value-lattice&quot; target=&quot;_blank&quot;&gt;решеток&lt;/a&gt;: значения в CUE образуют решетку, где любые два значения имеют уникальное наиболее конкретное значение, которое обобщает оба (наименьшая верхняя граница), и уникальное наиболее общее значение, которое специализируется на обоих (наибольшая нижняя граница).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Nrbd&quot;&gt;Cue может быть использован &lt;a href=&quot;https://cuelang.org/docs/integration/#technologies&quot; target=&quot;_blank&quot;&gt;для управления конфигурацией&lt;/a&gt; kubernetes, terraform, управление конфигурацией CI/CD gitlab, github.  &lt;/p&gt;
  &lt;p id=&quot;vt2B&quot;&gt;У CUE есть &lt;a href=&quot;https://cuelang.org/docs/integration/&quot; target=&quot;_blank&quot;&gt;поддержка интеграций&lt;/a&gt; с YAML, JSON, Jsonschema, OpenAPI, Go (для Go есть даже &lt;a href=&quot;https://cuelang.org/docs/concept/code-generation-and-extraction-use-case/&quot; target=&quot;_blank&quot;&gt;кодогенерация и извлечение данных&lt;/a&gt;), Protobuf и Java.&lt;/p&gt;
  &lt;p id=&quot;P3wJ&quot;&gt;Пример валидации IP адреса&lt;/p&gt;
  &lt;pre id=&quot;00u9&quot;&gt;package example

import &amp;quot;net&amp;quot;

[_]: net.IPv4

v4String: &amp;quot;198.51.100.14&amp;quot;
v4Bytes: [198, 51, 100, 14]

// невалидные ip адреса
tooManyOctets: &amp;quot;198.51.100.14.0&amp;quot;
octetTooLarge: [300, 51, 100, 14]
v6NotV4: &amp;quot;2001:0db8:85a3::8a2e:0370:7334&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;2lgd&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;7mgP&quot;&gt;В терминале вводим&lt;/p&gt;
  &lt;pre id=&quot;hf1j&quot;&gt;cue vet -c
octetTooLarge: invalid value [300,51,100,14] (does not satisfy net.IPv4):
    ./file.cue:6:6
    ./file.cue:14:16
tooManyOctets: invalid value &amp;quot;198.51.100.14.0&amp;quot; (does not satisfy net.IPv4):
    ./file.cue:6:6
    ./file.cue:13:16
v6NotV4: invalid value &amp;quot;2001:0db8:85a3::8a2e:0370:7334&amp;quot; (does not satisfy net.IPv4):
    ./file.cue:6:6
    ./file.cue:15:10&lt;/pre&gt;
  &lt;p id=&quot;cTcZ&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;9SOk&quot;&gt;Пример валидации конфигурации с использованием CUE&lt;/p&gt;
  &lt;p id=&quot;BC7T&quot;&gt;check.cue&lt;/p&gt;
  &lt;pre id=&quot;FqdI&quot;&gt;Workflow: {
  jobs: deploy: {
    environment!: string
    // для окружения production запускать нужно на ubuntu-latest
    if environment == &amp;quot;production&amp;quot; {
      &amp;quot;runs-on&amp;quot;!: &amp;quot;ubuntu-latest&amp;quot;
    }
  }
}

.github/workflows/deploy-to-ecs.yml

name: Deploy to Amazon ECS

on:
  push:
  branches: [ $default-branch ]
  
env:
  AWS_REGION: MY_AWS_REGION
  ECR_REPOSITORY: MY_ECR_REPOSITORY
  ECS_SERVICE: MY_ECS_SERVICE
  ECS_CLUSTER: MY_ECS_CLUSTER
  ECS_TASK_DEFINITION: MY_ECS_TASK_DEFINITION
  CONTAINER_NAME: MY_CONTAINER_NAME
  
permissions:
  contents: read

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-20.04
    environment: production
    steps:
    - name: Checkout
      uses: actions/checkout@v3
      
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ env.AWS_REGION }}
        
    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1
     
    - name: Build, tag, and push image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        echo &amp;quot;image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG&amp;quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT

    - name: Fill in the new image ID in the Amazon ECS task definition
      id: task-def
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      with:
        task-definition: ${{ env.ECS_TASK_DEFINITION }}
        container-name: ${{ env.CONTAINER_NAME }}
        image: ${{ steps.build-image.outputs.image }}
        
    - name: Deploy Amazon ECS task definition
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.task-def.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE }}
        cluster: ${{ env.ECS_CLUSTER }}
        wait-for-service-stability: true&lt;/pre&gt;
  &lt;p id=&quot;8gMF&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;aQ5P&quot;&gt;Запуск cue&lt;/p&gt;
  &lt;pre id=&quot;qjPX&quot;&gt;$ cue vet -c check.cue .github/workflows/deploy-to-ecs.yml -d &amp;#x27;Workflow&amp;#x27;
jobs.deploy.&amp;quot;runs-on&amp;quot;: conflicting values &amp;quot;ubuntu-latest&amp;quot; and &amp;quot;ubuntu-20.04&amp;quot;:
    .github/workflows/deploy-to-ecs.yml:22:14
    ./check.cue:6:3
    ./check.cue:7:16&lt;/pre&gt;
  &lt;p id=&quot;UlPR&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;lflh&quot;&gt;Итоги&lt;/h3&gt;
  &lt;ul id=&quot;f9GN&quot;&gt;
    &lt;li id=&quot;cJuP&quot;&gt;KDL: простой синтаксис со структурой на основе узлов. Отлично подходит для сценариев, где удобство чтения человеком имеет первостепенное значение. Простота KDL - его сильная сторона.&lt;/li&gt;
    &lt;li id=&quot;FTQs&quot;&gt;JSONNET подходит для конфигураций с большим количеством шаблонов.&lt;/li&gt;
    &lt;li id=&quot;sYMo&quot;&gt;Dhall больше подходит для конфигураций, где корректность и безопасность имеют решающее значение, а также там, где для разработчиков будет более близким и знакомым функциональный стиль программирования.&lt;/li&gt;
    &lt;li id=&quot;qWKw&quot;&gt;KCL подойдет для cloud-native приложений со сложными требованиями к проверке.&lt;/li&gt;
    &lt;li id=&quot;5NfK&quot;&gt;CUE подойдет в случаях, где требуется унифицированная схема и конфигурация со строгими ограничениями.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;9R0q&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;IS94&quot;&gt;Дополнительные материалы&lt;/h3&gt;
  &lt;ul id=&quot;oAN8&quot;&gt;
    &lt;li id=&quot;mDts&quot;&gt;Как описать 100 Gitlab джоб в 100 строк JSONNET &lt;a href=&quot;https://habr.com/ru/articles/483626/&quot; target=&quot;_blank&quot;&gt;https://habr.com/ru/articles/483626/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;ydp0&quot;&gt;Развертывание программных систем в Kubernetes c помощью JSONNET &lt;a href=&quot;https://habr.com/ru/articles/720556/&quot; target=&quot;_blank&quot;&gt;https://habr.com/ru/articles/720556/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;YaJj&quot;&gt;Официальный сайт KDL &lt;a href=&quot;https://kdl.dev/&quot; target=&quot;_blank&quot;&gt;https://kdl.dev/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;d3DL&quot;&gt;Официальный сайт JSONNET &lt;a href=&quot;https://jsonnet.org/&quot; target=&quot;_blank&quot;&gt;https://jsonnet.org/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;mcQH&quot;&gt;Официальный сайт KCL &lt;a href=&quot;https://www.kcl-lang.io/&quot; target=&quot;_blank&quot;&gt;https://www.kcl-lang.io/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;IHuE&quot;&gt;Официальный сайт CUE &lt;a href=&quot;https://cuelang.org/&quot; target=&quot;_blank&quot;&gt;https://cuelang.org/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;2KXP&quot;&gt;Cтатья по конфигурации Kubernetes c использованием CUE - &lt;a href=&quot;https://engineering.mercari.com/en/blog/entry/20220127-kubernetes-configuration-management-with-cue/&quot; target=&quot;_blank&quot;&gt;https://engineering.mercari.com/en/blog/entry/20220127-kubernetes-configuration-management-with-cue/&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;

</content></entry><entry><id>rnds:065-ebpf-tools</id><link rel="alternate" type="text/html" href="https://blog.rnds.pro/065-ebpf-tools?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rnds"></link><title>Руководство пасечника или обзор инструментария eBPF</title><published>2025-04-29T19:34:35.611Z</published><updated>2025-05-01T19:07:19.415Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/d5/c9/d5c95bb7-a299-40b4-981a-8b7afeff7cca.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/05/68/05683514-bcad-4f26-9efb-afbcdf0a0992.jpeg&quot;&gt;Я во время съемок фильма Пчеловод 3 раза запустил bpftrace и ни разу об это не пожалел. 

Джейсон Стейтем</summary><content type="html">
  &lt;blockquote id=&quot;QJg4&quot; data-align=&quot;right&quot;&gt;&lt;em&gt;Я во время съемок фильма Пчеловод 3 раза запустил bpftrace и ни разу об это не пожалел. &lt;br /&gt;&lt;br /&gt;Джейсон Стейтем&lt;/em&gt;&lt;/blockquote&gt;
  &lt;figure id=&quot;dInU&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/05/68/05683514-bcad-4f26-9efb-afbcdf0a0992.jpeg&quot; width=&quot;1043&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;fVZT&quot;&gt;&lt;strong&gt;Введение&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;uwVN&quot;&gt;Ранее мы уже &lt;a href=&quot;https://blog.rnds.pro/056-all-you-know-about-ebpf&quot; target=&quot;_blank&quot;&gt;рассказывали&lt;/a&gt; кратко про то, что такое eBPF. В этой статье мы посмотрим на экосистему вокруг eBPF и на инструментарий, который используется для эксплуатации и разработки программ eBPF. Вкратце постараемся привести некоторые примеры использования данных инструментов.&lt;/p&gt;
  &lt;p id=&quot;TzU6&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;1q95&quot;&gt;&lt;strong&gt;bpftrace&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;uhDa&quot;&gt;Начнем с самой простой утилиты, которая используется для того, чтобы взаимодействовать с eBPF. &lt;a href=&quot;https://brendangregg.com/ebpf.html&quot; target=&quot;_blank&quot;&gt;Брендан Грегг&lt;/a&gt;  в своем блоге ставит эту утилиту на первое место по легкости освоения. Поставить bpftrace, ни у кого думаю, &lt;a href=&quot;https://github.com/bpftrace/bpftrace/blob/master/INSTALL.md#package-install&quot; target=&quot;_blank&quot;&gt;не составит труда&lt;/a&gt;. После установки в /usr/sbin появятся небольшие файлы примеров с расширением .bt, с которыми можно поиграться. Исходный код и небольшие описания есть на github &lt;a href=&quot;https://github.com/bpftrace/bpftrace/blob/master/tools/README.md&quot; target=&quot;_blank&quot;&gt;проекта.&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;JFOz&quot;&gt;Также &lt;a href=&quot;https://www.brendangregg.com/BPF/bpftrace-cheat-sheet.html&quot; target=&quot;_blank&quot;&gt;хороший cheat sheet&lt;/a&gt; есть на сайте Брендана Грегга: &lt;/p&gt;
  &lt;p id=&quot;og5R&quot;&gt;&lt;/p&gt;
  &lt;figure id=&quot;S024&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXcFiv8vxpmtmMdhmzcjpD3jrZef3eZ98l9Xknu-XFS3lRqWk1VNLZZCuDirqVmy2Vpny8eh_S0F_D1yJzCD2f_H5rDfr7Vv9fjMb9YdXW23q34GB2vtdu4Od8rcd7TSZ_X2_enJ9NVVdG3X3XNyZeDm7Paf?key=_ZZ_sAP3atMvWDb_pwJhiw&quot; width=&quot;602&quot; /&gt;
    &lt;figcaption&gt;Картинка взята из репозитория bpftrace &lt;strong&gt;&lt;a href=&quot;https://github.com/bpftrace/bpftrace/blob/master/images/bpftrace_probes_2018.png&quot; target=&quot;_blank&quot;&gt;https://github.com/bpftrace/bpftrace/blob/master/images/bpftrace_probes_2018.png&lt;/a&gt;&lt;/strong&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;GOR8&quot;&gt;&lt;/p&gt;
  &lt;figure id=&quot;geJ6&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXds6NFds2mD1xXkg2f-y3xrLFQq8RjULYo8Eho7o_sCAtOfSRgW63NXH9WcacB4lpri5G91bvZj3lo0jE78bLGKEB9G-b1Usoj27fsdV_F5W2qswl57TKbXL5sSx1wads__amuF8w?key=_ZZ_sAP3atMvWDb_pwJhiw&quot; width=&quot;602&quot; /&gt;
    &lt;figcaption&gt;Картинка взята из книги BPF Performance Tools Brendan Gregg 2019&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;BiVl&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;H4tT&quot;&gt;bpftrace предоставляет простой синтаксис для написания однострочных команд или более сложных скриптов, позволяющих отслеживать и анализировать поведение ядра и приложений в режиме реального времени.&lt;/p&gt;
  &lt;p id=&quot;nltr&quot;&gt;С помощью bpftrace можно:&lt;/p&gt;
  &lt;ul id=&quot;N1Lu&quot;&gt;
    &lt;li id=&quot;f3EV&quot;&gt;Мониторить системные вызовов&lt;/li&gt;
    &lt;li id=&quot;cPTb&quot;&gt;Анализировать задержки ввода-вывода&lt;/li&gt;
    &lt;li id=&quot;yska&quot;&gt;Профилировать использование CPU и памяти&lt;/li&gt;
    &lt;li id=&quot;O1Ui&quot;&gt;Отслеживать сетевую активность&lt;/li&gt;
    &lt;li id=&quot;aiXZ&quot;&gt;Диагностировать проблемы производительности&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;KX3n&quot;&gt;Давайте теперь взглянем на bpftrace в действии.&lt;/p&gt;
  &lt;p id=&quot;3dZ1&quot;&gt;Этот скрипт в реальном времени мониторит создание новых процессов. Команда печатает текущее время, имя процесса, PID и родительский PID, str(args-&amp;gt;filename) конвертирует указатель на файл в строку&lt;/p&gt;
  &lt;pre id=&quot;wvyd&quot;&gt;bpftrace -e &amp;#x27;tracepoint:sched:sched_process_exec {time(&amp;quot;%H:%M:%S &amp;quot;); printf(&amp;quot;Process exec: %s (PID: %d, PPID: %d)\n&amp;quot;, str(args-&amp;gt;filename), pid, curtask-&amp;gt;parent-&amp;gt;pid); }&amp;#x27;&lt;/pre&gt;
  &lt;p id=&quot;d790&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;l2YV&quot;&gt;Профилирование системных вызовов по процессу.&lt;/p&gt;
  &lt;p id=&quot;hCwQ&quot;&gt;Этот скрипт профилирует системный вызов sys_enter и с интервалом каждые 10 секунд выводит кол-во системных вызовов sys_enter, а также PID процесса и имя процесса.&lt;/p&gt;
  &lt;pre id=&quot;wdbE&quot;&gt;bpftrace -e &amp;#x27;tracepoint:raw_syscalls:sys_enter {@syscalls[pid, comm, args-&amp;gt;id] = count();} interval:s:10 { print(@syscalls); clear(@syscalls); }&amp;#x27;&lt;/pre&gt;
  &lt;p id=&quot;dcyH&quot;&gt;Этот скрипт мониторит кол-во переключений контекста между 2-мя процессами prev_comm - процесс, который запущен перед переключением контекста, а next_comm - процесс, что будет запущен после переключения контекста.&lt;/p&gt;
  &lt;pre id=&quot;wWte&quot;&gt;bpftrace -e &amp;#x27;tracepoint:sched:sched_switch {@task_switches[args-&amp;gt;prev_comm, args-&amp;gt;next_comm] = count();}&amp;#x27;&lt;/pre&gt;
  &lt;p id=&quot;MlWH&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;tn7j&quot;&gt;Несколько полезных замечаний:&lt;/p&gt;
  &lt;ul id=&quot;N7OO&quot;&gt;
    &lt;li id=&quot;ngaI&quot;&gt;У bpftrace есть флаг -d для запуска в режиме dry-run.&lt;/li&gt;
    &lt;li id=&quot;Prdu&quot;&gt;Для сложных скриптов лучше создавать отдельные файлы .bt. Загляните как это сделано в файлах /usr/sbin/*.bt&lt;/li&gt;
    &lt;li id=&quot;SjHR&quot;&gt;Для длинных сессий сбора данных используйте interval&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;QikF&quot;&gt;Когда использовать bpftrace:&lt;/p&gt;
  &lt;ul id=&quot;n5DG&quot;&gt;
    &lt;li id=&quot;8cVm&quot;&gt;быстрый обзор системы по проблеме производительности&lt;/li&gt;
    &lt;li id=&quot;1t6X&quot;&gt;однострочные и простые скрипты&lt;/li&gt;
    &lt;li id=&quot;YEx0&quot;&gt;когда достаточно будет простых агрегаций, которые может предоставить bpftrace: подсчет, гистограммы и простая статистика&lt;/li&gt;
    &lt;li id=&quot;MRXU&quot;&gt;с bpftrace не нужно иметь опыт программирования&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;gyYx&quot;&gt;Кроме того у bpftrace есть свой &lt;a href=&quot;https://bpftrace.org/&quot; target=&quot;_blank&quot;&gt;сайт&lt;/a&gt;, где есть страничка с &lt;a href=&quot;https://bpftrace.org/one-liners&quot; target=&quot;_blank&quot;&gt;однострочниками&lt;/a&gt;, &lt;a href=&quot;https://bpftrace.org/tutorial-one-liners&quot; target=&quot;_blank&quot;&gt;туториалом по однострочникам&lt;/a&gt;, &lt;a href=&quot;https://bpftrace.org/hol/intro&quot; target=&quot;_blank&quot;&gt;страничка с практическими занятиями&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;e7N9&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;gX35&quot;&gt;bcc&lt;/h3&gt;
  &lt;p id=&quot;Pust&quot;&gt;BCC (BPF Compiler Collection) - это набор инструментов и библиотек для создания эффективных программ eBPF. BCC предоставляет Python и Lua интерфейсы для написания программ, которые компилируются в eBPF-код &amp;quot;на лету&amp;quot; при запуске.&lt;/p&gt;
  &lt;p id=&quot;EGhf&quot;&gt;Инструкцию по установке для различных дистрибутивов можно найти по &lt;a href=&quot;https://github.com/iovisor/bcc/blob/master/INSTALL.md&quot; target=&quot;_blank&quot;&gt;ссылке.&lt;/a&gt; &lt;/p&gt;
  &lt;p id=&quot;KVlW&quot;&gt;Также как и для bpftrace на основе BCC написано множество полезных утилит с исходным кодом, ознакомиться можно по &lt;a href=&quot;https://github.com/iovisor/bcc/tree/master/tools&quot; target=&quot;_blank&quot;&gt;ссылке&lt;/a&gt;. А огромное кол-во примеров для этих утилит можно найти &lt;a href=&quot;https://github.com/iovisor/bcc/tree/master?tab=readme-ov-file#tools&quot; target=&quot;_blank&quot;&gt;здесь.&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;PTi1&quot;&gt;Также есть полезный &lt;a href=&quot;https://github.com/iovisor/bcc/blob/master/docs/tutorial.md&quot; target=&quot;_blank&quot;&gt;туториал для bcc.&lt;/a&gt; &lt;/p&gt;
  &lt;p id=&quot;6Ikz&quot;&gt;Отдельно &lt;a href=&quot;https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md&quot; target=&quot;_blank&quot;&gt;для Python разработчиков.&lt;/a&gt;&lt;br /&gt;&lt;/p&gt;
  &lt;p id=&quot;1lmz&quot;&gt;Ниже на скрине представлены области применения готовых BCC инструментов:&lt;/p&gt;
  &lt;figure id=&quot;HoiB&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXcjeHcSMTM_i6c6jOM3WZ4y3nVxXd2c1-Wf_anUn0kTuvjfJxHgc8C484Bii2uoY1vqdov7BaADr0Yp4M0W0CO-hgBHw6w40UpB3f8qCAY0OrMUmk4h0Qqzw7DX_-rT81RbKOrRlA?key=_ZZ_sAP3atMvWDb_pwJhiw&quot; width=&quot;602&quot; /&gt;
    &lt;figcaption&gt;Картинка взята из репозитория bcc &lt;strong&gt;&lt;a href=&quot;https://github.com/iovisor/bcc/blob/master/images/bcc_tracing_tools_2019.png&quot; target=&quot;_blank&quot;&gt;https://github.com/iovisor/bcc/blob/master/images/bcc_tracing_tools_2019.png&lt;/a&gt;&lt;/strong&gt;&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;aY2n&quot;&gt;&lt;/p&gt;
  &lt;pre id=&quot;XQGN&quot;&gt;execsnoop&lt;/pre&gt;
  &lt;p id=&quot;vHic&quot;&gt;&lt;a href=&quot;https://github.com/iovisor/bcc/blob/master/tools/execsnoop.py&quot; target=&quot;_blank&quot;&gt;инструмент из набора BCC&lt;/a&gt;, который отслеживает все новые системные вызовы exec(). Он показывает в реальном времени, какие программы запускаются, кем они запускаются (PID, PPID) и с какими аргументами. Это полезно для понимания того, что происходит в системе, особенно когда программы запускаются автоматически или через скрипты.&lt;/p&gt;
  &lt;pre id=&quot;AryD&quot;&gt;biolatency -D 5 &lt;/pre&gt;
  &lt;p id=&quot;WUEj&quot;&gt;&lt;a href=&quot;https://github.com/iovisor/bcc/blob/master/tools/biolatency.py&quot; target=&quot;_blank&quot;&gt;biolatency&lt;/a&gt; измеряет время выполнения блочных операций ввода-вывода и отображает их в виде гистограммы. Опция -D 5 указывает выводить обновленную гистограмму каждые 5 секунд. Это помогает выявить проблемы с дисковой подсистемой, такие как медленные диски или неэффективные шаблоны доступа к данным.&lt;/p&gt;
  &lt;p id=&quot;DF4n&quot;&gt;Если у Вас debian, то возможно придется воспользоваться &lt;a href=&quot;https://github.com/iovisor/bcc/issues/2278#issuecomment-1582090173&quot; target=&quot;_blank&quot;&gt;подсказкой&lt;/a&gt;, иначе будете получать ошибку:&lt;/p&gt;
  &lt;pre id=&quot;FYxf&quot;&gt;Traceback (most recent call last):
File &amp;quot;/path/of/bitehist.py&amp;quot;, line 17, in &amp;lt;module&amp;gt;
ImportError: cannot import name &amp;#x27;BPF&amp;#x27; from &amp;#x27;bcc&amp;#x27; 
(/opt/python/3.9.6/lib/python3.9/site-packages/bcc/__init__.py)&lt;/pre&gt;
  &lt;p id=&quot;vjEf&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;v0bN&quot;&gt;Далее напишем, что-нибудь свое:&lt;/p&gt;
  &lt;p id=&quot;XNvv&quot;&gt;&lt;/p&gt;
  &lt;pre id=&quot;IIFn&quot;&gt;from bcc import BPF
from time import sleep

# Определяем BPF программу
# Импортируем необходимые заголовочные файлы
bpf_text = &amp;quot;&amp;quot;&amp;quot;
#include &amp;lt;uapi/linux/ptrace.h&amp;gt;
#include &amp;lt;linux/sched.h&amp;gt;

// Определяем структуру для хранения PID и имени процесса
struct key_t {
  u32 pid;
  char comm[TASK_COMM_LEN];
};

// Создаем хэш мапу для хранения последнего timestamp для каждого PID
BPF_HASH(last_time, u32);

// Создаем хэш мапу для хранения времени выполнения
BPF_HASH(data, struct key_t, u64);

// Аттачимся к точке трассировки sched_switch в планировщике ядра, 
// которая срабатывает, когда происходит переключение планировщика между 
// процессами
TRACEPOINT_PROBE(sched, sched_switch) {

// получаем PID следующего для запуска процесса
// получаем текущий timestamp в наносекундах
// получаем последнюю временную метку для этого PID
u32 pid = args-&amp;gt;next_pid;
u64 ts = bpf_ktime_get_ns();
u64 *last = last_time.lookup(&amp;amp;pid);
  
// проверяем если есть предыдущий timestamp для PID, то вычисляем дельту 
// между текущим timestamp и последним запуском, создаем key c PID и 
// именем процесса, сохраняем дельту в data
if (last) {
  u64 delta = ts - *last;
  struct key_t key = {};
  key.pid = pid;
  bpf_get_current_comm(&amp;amp;key.comm, sizeof(key.comm));
  data.update(&amp;amp;key, &amp;amp;delta);
}

// Обновляем последний timestamp для этого PID
last_time.update(&amp;amp;pid, &amp;amp;ts);
return 0;
}
&amp;quot;&amp;quot;&amp;quot;
# Загружаем BPF программу, тут ранее написанная BPF программа компилируется 
# и загружается в ядро.
b = BPF(text=bpf_text)

print(&amp;quot;Отслеживание времени выполнения процессов... Нажмите Ctrl+C для завершения.&amp;quot;)

# Запускаем в бесконечном цикле и выводим PID, COMMAND и RUNTIME (в миллисекундах). Итерируемся по data хэш мапе.
try:
  while True:
    sleep(1)
    print(&amp;quot;\n%-6s %-16s %-16s&amp;quot; % (&amp;quot;PID&amp;quot;, &amp;quot;COMM&amp;quot;, &amp;quot;RUNTIME (ms)&amp;quot;))
    for k, v in b[&amp;quot;data&amp;quot;].items():
      print(&amp;quot;%-6d %-16s %-16.2f&amp;quot; % (k.pid, k.comm.decode(&amp;#x27;utf-8&amp;#x27;, &amp;#x27;replace&amp;#x27;), v.value / 1000000))
      b[&amp;quot;data&amp;quot;].clear()
except KeyboardInterrupt:
  print(&amp;quot;Выходим...&amp;quot;)&lt;/pre&gt;
  &lt;p id=&quot;1OMe&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;0a0e&quot;&gt;Этот скрипт отслеживает время выполнения процессов, используя точку трассировки shed_switch, которая вызывается при переключении задач. Для каждого процесса скрипт сохраняет время последнего запуска и вычисляет разницу между текущим и предыдущим запуском. Результаты выводятся каждую секунду, показывая PID процесса, имя процесса и время его выполнения в миллисекундах.&lt;/p&gt;
  &lt;p id=&quot;LSHE&quot;&gt;Несколько полезных замечаний:&lt;/p&gt;
  &lt;ul id=&quot;Ma6z&quot;&gt;
    &lt;li id=&quot;DFm2&quot;&gt;Используйте готовые инструменты BCC для стандартных задач трассировки&lt;/li&gt;
    &lt;li id=&quot;Vtoc&quot;&gt;Используйте агрегацию данных (карты, гистограммы) вместо трассировки каждого события&lt;/li&gt;
    &lt;li id=&quot;wQEC&quot;&gt;Учитывайте, что BCC требует установленных заголовков ядра и компилятора LLVM/Clang&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;chFe&quot;&gt;Когда использовать bcc:&lt;/p&gt;
  &lt;ul id=&quot;IpYU&quot;&gt;
    &lt;li id=&quot;EvQm&quot;&gt;Когда нужен более сложный анализ, чем могут дать однострочники bpftrace&lt;/li&gt;
    &lt;li id=&quot;8rhA&quot;&gt;Когда нужны долгосрочные решения для мониторинга, в т.ч. утилиты для многократного использования&lt;/li&gt;
    &lt;li id=&quot;DepZ&quot;&gt;Если нужно интегрировать eBPF инструменты с другими системами, в т.ч. с системами мониторинга&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;qJ4u&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;BOpG&quot;&gt;&lt;strong&gt;libbpf&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;NG1M&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;l3Qz&quot;&gt;libbpf - это C библиотека для работы с eBPF программами, которая позволяет загружать, верифицировать и управлять eBPF программами из пользовательского пространства. В отличие от BCC, libbpf не требует компилятора во время выполнения, что делает ее более подходящей для производственных сред. libbpf нужен, когда нужно выжать максимум из тех возможностей, которые предоставляет eBPF&lt;/p&gt;
  &lt;ul id=&quot;yJQf&quot;&gt;
    &lt;li id=&quot;WlsA&quot;&gt;Высокопроизводительная обработка сетевого трафика&lt;/li&gt;
    &lt;li id=&quot;5rn1&quot;&gt;Долгосрочный мониторинг системы&lt;/li&gt;
    &lt;li id=&quot;dUiD&quot;&gt;Обнаружение аномалий и угроз безопасности&lt;/li&gt;
    &lt;li id=&quot;1srv&quot;&gt;Сбор метрик производительности с минимальными накладными расходами&lt;/li&gt;
    &lt;li id=&quot;U51o&quot;&gt;Расширение возможностей ядра без модификации исходного кода&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;2hiX&quot;&gt;Для примера libbpf возьмем простейший пример из &lt;a href=&quot;https://github.com/libbpf/libbpf-bootstrap&quot; target=&quot;_blank&quot;&gt;репозитория&lt;/a&gt; &lt;/p&gt;
  &lt;p id=&quot;IQXY&quot;&gt;Нужно установить сперва пакеты, которые нужны для компиляции&lt;/p&gt;
  &lt;pre id=&quot;cdLR&quot;&gt;apt-get install -y build-essential clang llvm libelf-dev libcap-dev&lt;/pre&gt;
  &lt;p id=&quot;5Gv8&quot;&gt;клонируем репозиторий &lt;/p&gt;
  &lt;pre id=&quot;v64J&quot;&gt;git clone https://github.com/libbpf/libbpf-bootstrap.git&lt;/pre&gt;
  &lt;p id=&quot;Bbcr&quot;&gt;заходим в папку&lt;/p&gt;
  &lt;pre id=&quot;ViSF&quot;&gt;cd libbpf-bootstrap/examples/c/&lt;/pre&gt;
  &lt;p id=&quot;LJnE&quot;&gt;Рассмотрим более подробно исходный код нашего примера&lt;/p&gt;
  &lt;p id=&quot;VRtu&quot;&gt;minimal.bpf.c&lt;/p&gt;
  &lt;pre id=&quot;v7e3&quot;&gt;// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2020 Facebook */
// Импортируем заголовочные файлы
#include &amp;lt;linux/bpf.h&amp;gt;
#include &amp;lt;bpf/bpf_helpers.h&amp;gt;

// Это строка необходима для загрузки eBPF программы. Ядро загружает 
// программы только с совместимой лицензией.
char LICENSE[] SEC(&amp;quot;license&amp;quot;) = &amp;quot;Dual BSD/GPL&amp;quot;;

// Переменная, которая нам потребуется в пользовательском пространстве
int my_pid = 0;

// Это прикрепляет программу к точки трассировки, которая
// срабатывает, когда процесс вызывает системный вызов write()
SEC(&amp;quot;tp/syscalls/sys_enter_write&amp;quot;)

// Функция, которая будет вызываться при каждом достижении точки трассировки.
// Функция извлекает PID текущего процесса, проверяет, что PID соответствует целевому PID my_pid, 
// если они совпадают, то логирует сообщение, используя bpf_printk, которая пишет в trace pipe
int handle_tp(void *ctx)
{
  int pid = bpf_get_current_pid_tgid() &amp;gt;&amp;gt; 32;
  if (pid != my_pid)
    return 0;
  bpf_printk(&amp;quot;BPF triggered from PID %d.\n&amp;quot;, pid);
  return 0;
}
&lt;/pre&gt;
  &lt;p id=&quot;Kz2S&quot;&gt;minimal.c - это программа пользовательского пространства ядра, которая будет загружать и управлять eBPF программой.&lt;/p&gt;
  &lt;p id=&quot;HxxM&quot;&gt;Как видно мы импортируем minimal.skel.h это файл, который во время компиляции автоматически генерируется BPFTool, его мы не будем рассматривать, он после компиляции находится в каталоге рядом .output&lt;/p&gt;
  &lt;p id=&quot;oTl0&quot;&gt;minimal.c&lt;/p&gt;
  &lt;pre id=&quot;YsS2&quot;&gt;// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2020 Facebook */
// Импортируем заголовочные файлы
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;sys/resource.h&amp;gt;
#include &amp;lt;bpf/libbpf.h&amp;gt;
#include &amp;quot;minimal.skel.h&amp;quot;

/* Функция callback, которая принимает libbpf log level, строку
format и переменные аргументы. Перенаправляет libbpf логи в
stderr */
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
  return vfprintf(stderr, format, args);
}

int main(int argc, char **argv)
{
/* Определяем указатель на skeleton структуру и целое число для
хранения кода ошибки. */
  struct minimal_bpf *skel;
  int err;

/* Регистрируем нашу функцию callback для обработки сообщений libbpf */
  libbpf_set_print(libbpf_print_fn);
  
/* Открываем BPF приложение */
  skel = minimal_bpf__open();
  if (!skel) {
    fprintf(stderr, &amp;quot;Failed to open BPF skeleton\n&amp;quot;);
    return 1;
}

/* Убеждаемся, что BPF программа обрабатывает только системные вызовы write() */
  skel-&amp;gt;bss-&amp;gt;my_pid = getpid();

/* Загружаем программу (на этом этапе также проходит проверка верификатором ядра, чтобы гарантировать безопасность BPF программы */
  err = minimal_bpf__load(skel);
  if (err) {
    fprintf(stderr, &amp;quot;Failed to load and verify BPF skeleton\n&amp;quot;);
    goto cleanup;
}

/* Прикрепляем загруженную BPF программу к точки трассировки в ядре */

  err = minimal_bpf__attach(skel);
  if (err) {
    fprintf(stderr, &amp;quot;Failed to attach BPF skeleton\n&amp;quot;);
    goto cleanup;
  }

  printf(&amp;quot;Successfully started! Please run &amp;#x60;sudo cat /sys/kernel/debug/tracing/trace_pipe&amp;#x60; &amp;quot;
&amp;quot;to see output of the BPF programs.\n&amp;quot;);
  for (;;) {
    /* Пишем в файл из нашей BPF программы */
    fprintf(stderr, &amp;quot;.&amp;quot;);
    sleep(1);
}

/* cleanup блок, в который переходим в случае ошибки или прерывания программы */
cleanup:
  minimal_bpf__destroy(skel);
  return -err;
}&lt;/pre&gt;
  &lt;p id=&quot;ajGv&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;VAPz&quot;&gt;Исходный код рассмотрели, теперь компилируем&lt;/p&gt;
  &lt;pre id=&quot;9LMR&quot;&gt;make&lt;/pre&gt;
  &lt;p id=&quot;eJUu&quot;&gt;и запускаем&lt;/p&gt;
  &lt;pre id=&quot;Klds&quot;&gt;./minimal&lt;/pre&gt;
  &lt;p id=&quot;OVl4&quot;&gt;После запуска &lt;/p&gt;
  &lt;pre id=&quot;Lurg&quot;&gt;
cat /sys/kernel/debug/tracing/trace_pipe&lt;/pre&gt;
  &lt;p id=&quot;Jfwj&quot;&gt;&lt;/p&gt;
  &lt;pre id=&quot;xnZ5&quot;&gt;minimal-1064528 [002] d..31 1761398.028307: bpf_trace_printk: BPF triggered from PID 1064528.
minimal-1064528 [002] d..31 1761399.028377: bpf_trace_printk: BPF triggered from PID 1064528.
minimal-1064528 [002] d..31 1761400.028446: bpf_trace_printk: BPF triggered from PID 1064528.
minimal-1064528 [002] d..31 1761401.028530: bpf_trace_printk: BPF triggered from PID 1064528.
minimal-1064528 [002] d..31 1761402.028585: bpf_trace_printk: BPF triggered from PID 1064528.
minimal-1064528 [002] d..31 1761403.028679: bpf_trace_printk: BPF triggered from PID 1064528.
minimal-1064528 [002] d..31 1761404.028765: bpf_trace_printk: BPF triggered from PID 1064528.
minimal-1064528 [002] d..31 1761405.028851: bpf_trace_printk: BPF triggered from PID 1064528.
minimal-1064528 [002] d..31 1761406.028941: bpf_trace_printk: BPF triggered from PID 1064528.
minimal-1064528 [002] d..31 1761407.029029: bpf_trace_printk: BPF triggered from PID 1064528.
minimal-1064528 [002] d..31 1761408.029124: bpf_trace_printk: BPF triggered from PID 1064528.
minimal-1064528 [002] d..31 1761409.029218: bpf_trace_printk: BPF triggered from PID 1064528.
minimal-1064528 [002] d..31 1761410.029309: bpf_trace_printk: BPF triggered from PID 1064528.&lt;/pre&gt;
  &lt;p id=&quot;QYAS&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;mOxj&quot;&gt;Заключение&lt;/h3&gt;
  &lt;p id=&quot;MfAE&quot;&gt;&lt;strong&gt;bpftrace&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;Xq2b&quot;&gt;bpftrace — идеальный инструмент для быстрой диагностики проблем и создания ad-hoc инструментов трассировки. Его простой синтаксис и мощные возможности делают его незаменимым для системных администраторов и разработчиков, которым необходимо оперативно выяснить причину проблем производительности. Однако, bpftrace не лучший выбор для долгосрочных решений мониторинга или продакшен-окружений из-за потенциальных накладных расходов.&lt;/p&gt;
  &lt;p id=&quot;OVDN&quot;&gt;&lt;strong&gt;BCC Tools&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;rQSd&quot;&gt;BCC Tools предоставляет богатый набор готовых инструментов и Python API для создания собственных eBPF программ. Это делает его популярным среди разработчиков, которым нужна гибкость и возможность быстрого создания прототипов. BCC Tools лучше всего подходит для глубокой трассировки и исследования работы системы. Главным недостатком является зависимость от LLVM/Clang во время выполнения, что может быть проблематично в некоторых производственных средах.&lt;/p&gt;
  &lt;p id=&quot;u2Vi&quot;&gt;&lt;strong&gt;libbpf&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;2hIQ&quot;&gt;libbpf — наиболее эффективное и производительное решение для работы с eBPF. Отсутствие зависимостей от компилятора во время выполнения и поддержка &lt;a href=&quot;https://nakryiko.com/posts/bpf-core-reference-guide/&quot; target=&quot;_blank&quot;&gt;CO-RE&lt;/a&gt; делают его идеальным выбором для продакшен-систем и встраиваемых устройств. libbpf предлагает самый низкоуровневый API, что требует более глубокого понимания eBPF и C, но взамен даёт максимальный контроль и производительность. Это лучший выбор для долгосрочных решений мониторинга и наблюдаемости.&lt;/p&gt;
  &lt;p id=&quot;VCRY&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;p9CA&quot;&gt;Дополнительные материалы&lt;/h3&gt;
  &lt;ul id=&quot;Z16X&quot;&gt;
    &lt;li id=&quot;dGB8&quot;&gt;&lt;a href=&quot;https://github.com/cloudflare/ebpf_exporter&quot; target=&quot;_blank&quot;&gt;https://github.com/cloudflare/ebpf_exporter&lt;/a&gt; - ebpf exporter&lt;/li&gt;
    &lt;li id=&quot;8yH7&quot;&gt;&lt;a href=&quot;https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main&quot; target=&quot;_blank&quot;&gt;https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main&lt;/a&gt; - огромное кол-во уроков по ebpf&lt;/li&gt;
    &lt;li id=&quot;BoC6&quot;&gt;&lt;a href=&quot;https://habr.com/ru/articles/683566/&quot; target=&quot;_blank&quot;&gt;https://habr.com/ru/articles/683566/&lt;/a&gt; - хорошая статья про bcc tools и мониторинг dns запросов&lt;/li&gt;
    &lt;li id=&quot;jycD&quot;&gt;&lt;a href=&quot;https://eunomia.dev/tutorials/&quot; target=&quot;_blank&quot;&gt;https://eunomia.dev/tutorials/&lt;/a&gt; - очень много туториалов по libbpf&lt;/li&gt;
    &lt;li id=&quot;RCZg&quot;&gt;&lt;a href=&quot;https://nakryiko.com/posts/libbpf-bootstrap/&quot; target=&quot;_blank&quot;&gt;https://nakryiko.com/posts/libbpf-bootstrap/&lt;/a&gt; - статья про libbpf-bootstrap от разработчика ядра BPF&lt;/li&gt;
    &lt;li id=&quot;rDi8&quot;&gt;&lt;a href=&quot;https://nakryiko.com/posts/bpf-core-reference-guide/&quot; target=&quot;_blank&quot;&gt;https://nakryiko.com/posts/bpf-core-reference-guide/&lt;/a&gt; - Руководство по BPF CO-RE от разработчика ядра BPF&lt;/li&gt;
    &lt;li id=&quot;ivN2&quot;&gt;&lt;a href=&quot;https://www.piter.com/collection/linux/product/proizvoditelnost-sistem&quot; target=&quot;_blank&quot;&gt;https://www.piter.com/collection/linux/product/proizvoditelnost-sistem&lt;/a&gt; - легендарная книга System Performance от Брендана Грегга&lt;/li&gt;
    &lt;li id=&quot;BE3W&quot;&gt;&lt;a href=&quot;https://cilium.isovalent.com/hubfs/Learning-eBPF%20-%20Full%20book.pdf&quot; target=&quot;_blank&quot;&gt;https://cilium.isovalent.com/hubfs/Learning-eBPF%20-%20Full%20book.pdf&lt;/a&gt; - бесплатная книга Learning eBPF автор Liz Rice&lt;/li&gt;
    &lt;li id=&quot;bq9U&quot;&gt;&lt;a href=&quot;https://www.sobyte.net/post/2022-07/c-ebpf/&quot; target=&quot;_blank&quot;&gt;https://www.sobyte.net/post/2022-07/c-ebpf/&lt;/a&gt; - хорошая статья про libbpf&lt;/li&gt;
    &lt;li id=&quot;AYQ9&quot;&gt;Также у Брендана Грегга есть еще одна книга BPF Performance Tools Linux System and Application Observability&lt;/li&gt;
    &lt;li id=&quot;BnjT&quot;&gt;&lt;a href=&quot;https://github.com/eunomia-bpf/GPTtrace&quot; target=&quot;_blank&quot;&gt;https://github.com/eunomia-bpf/GPTtrace&lt;/a&gt; - интересный проект, который совмещает LLM и eBPF, трассировка и исследование linux с использованием естественного языка&lt;/li&gt;
    &lt;li id=&quot;OWrg&quot;&gt;&lt;a href=&quot;https://tetragon.io/&quot; target=&quot;_blank&quot;&gt;https://tetragon.io/&lt;/a&gt; - интересный проект для observability, безопасности и трассировки на основе eBPF от создателей Cilium CNI для Kubernetes&lt;/li&gt;
    &lt;li id=&quot;nWwv&quot;&gt;&lt;a href=&quot;https://www.brendangregg.com/ebpf.html&quot; target=&quot;_blank&quot;&gt;https://www.brendangregg.com/ebpf.html&lt;/a&gt; - блог Брендана Грегга по теме eBPF&lt;/li&gt;
    &lt;li id=&quot;zH1m&quot;&gt;&lt;a href=&quot;https://github.com/cloudflare/ebpf_exporter&quot; target=&quot;_blank&quot;&gt;https://github.com/cloudflare/ebpf_exporter&lt;/a&gt; - ebpf_exporter для создания кастомных метрик на базе eBPF&lt;/li&gt;
    &lt;li id=&quot;A51g&quot;&gt;&lt;a href=&quot;https://bpfman.io/main/&quot; target=&quot;_blank&quot;&gt;https://bpfman.io/main/&lt;/a&gt; - свежий проект под покровительством CNCF, ПО для запуска и управления eBPF программ&lt;/li&gt;
    &lt;li id=&quot;HAbH&quot;&gt;&lt;a href=&quot;https://nvd.codes/post/monitor-any-command-typed-at-a-shell-with-ebpf/&quot; target=&quot;_blank&quot;&gt;https://nvd.codes/post/monitor-any-command-typed-at-a-shell-with-ebpf/&lt;/a&gt; - статья, как мониторить все команды, вводимые в shell&lt;/li&gt;
    &lt;li id=&quot;FRqz&quot;&gt;&lt;a href=&quot;https://medium.com/all-things-ebpf&quot; target=&quot;_blank&quot;&gt;https://medium.com/all-things-ebpf&lt;/a&gt; - блог исключительно про eBPF&lt;/li&gt;
    &lt;li id=&quot;toU5&quot;&gt;&lt;a href=&quot;https://www.trackawesomelist.com/zoidbergwill/awesome-ebpf/readme/&quot; target=&quot;_blank&quot;&gt;https://www.trackawesomelist.com/zoidbergwill/awesome-ebpf/readme/&lt;/a&gt; - большой список дополнительных материалов по eBPF&lt;/li&gt;
  &lt;/ul&gt;

</content></entry><entry><id>rnds:064-devsecops1</id><link rel="alternate" type="text/html" href="https://blog.rnds.pro/064-devsecops1?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rnds"></link><title>DevSecOps подкрался незаметно, хотя заметен был издалека…</title><published>2025-02-11T12:34:47.613Z</published><updated>2025-02-13T11:29:40.709Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/16/a8/16a8c602-04e1-4b49-b0e0-eeb1761b331b.png"></media:thumbnail><tt:hashtag>docker</tt:hashtag><tt:hashtag>devsecops</tt:hashtag><tt:hashtag>ruby</tt:hashtag><tt:hashtag>cicd</tt:hashtag><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/42/f0/42f0e9b3-d3f8-403f-ac95-bfd789c92278.jpeg&quot;&gt;DevSecOps уверенно шагает по нашей индустрии, и горе тому, кто попадёт под его поступь… Эта статья про ультимативную сборку базовых образов для Ruby для удовлетворения самых параноидальных потребностей ИБ. Да, именно об этом мы и расскажем - что такое &quot;инсталляция ruby&quot;, где, что, почему лежит и как с этим жить нашему пайплайну сборки и самому приложению.</summary><content type="html">
  &lt;figure id=&quot;8fqy&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/42/f0/42f0e9b3-d3f8-403f-ac95-bfd789c92278.jpeg&quot; width=&quot;1043&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;LIkD&quot;&gt;DevSecOps уверенно шагает по нашей индустрии, и горе тому, кто попадёт под его поступь… Эта статья про &lt;s&gt;ультимативную&lt;/s&gt; сборку базовых образов для Ruby для удовлетворения самых параноидальных потребностей ИБ. Да, именно об этом мы и расскажем - что такое &amp;quot;инсталляция ruby&amp;quot;, где, что, почему лежит и как с этим жить нашему пайплайну сборки и самому приложению.  &lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;CaKt&quot;&gt;Статья подойдёт тем, кто хочет более глубоко понимать, какие процессы происходят в системе, когда вызывается &lt;code&gt;gem install&lt;/code&gt;, &lt;code&gt;bundle install&lt;/code&gt; или (не дай Бог) &lt;code&gt;gem update –system&lt;/code&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;h2 id=&quot;EYnh&quot;&gt;Как было до?&lt;/h2&gt;
  &lt;p id=&quot;FnX5&quot;&gt;С самого начала появления docker мы в RNDSOFT использовали парадигму &amp;quot;всё включено&amp;quot;. Для нас это означало, что в образ мы включаем всё, что нам надо не только для продуктовой эксплуатации, но и для проведения всех тестов. При прохождении CI пайплайна на следующие стадии продвигался образ целиком и, в конце концов, выкатывался на прод. &lt;/p&gt;
  &lt;p id=&quot;0jQ2&quot;&gt;Это было очень удобно и позволяло быть максимально (насколько это вообще возможно) уверенным в работоспособности, однако имело ряд серьёзных минусов, с которыми мы прекрасно жили достаточно долгое время:&lt;/p&gt;
  &lt;ul id=&quot;JDfC&quot;&gt;
    &lt;li id=&quot;RTeM&quot;&gt;размер образа был большим - от 1 ГБ;&lt;/li&gt;
    &lt;li id=&quot;uz3u&quot;&gt;образ включал большое количество неиспользуемого (в проде) ПО.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;qhEK&quot;&gt;Сканеры сканировали, сканировали, да не высканировали…&lt;/h2&gt;
  &lt;p id=&quot;71CC&quot;&gt;И вот однажды один (на данный момент уже далеко не один) наш клиент захотел устранения всех замечаний, которые смог выявить сканнер &lt;a href=&quot;https://github.com/aquasecurity/trivy&quot; target=&quot;_blank&quot;&gt;Trivy&lt;/a&gt;. А их, как не трудно догадаться, было достаточно много. И главная проблема заключается в том, что большая часть замечаний никак не связана с непосредственными зависимостями вашего приложения (теми, которые фиксируются в Gemfile.lock). &lt;br /&gt;Так откуда же они берутся? &lt;/p&gt;
  &lt;p id=&quot;YIEt&quot;&gt;Если коротко отвечать - отовсюду :) При сборке проекта необходимо поставить его зависимости - это план минимум. Кроме того, часто появляется необходимость установить bundle определенной версии или даже обновить ruby целиком, выполнив команду &lt;a href=&quot;https://guides.rubygems.org/command-reference/#gem-update&quot; target=&quot;_blank&quot;&gt;gem update --system&lt;/a&gt;. В результате этих действий в ваш образ в разнообразные папки ставятся разнообразные гемы, но&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;gt1O&quot;&gt;старые версии гемов и сама базовая &amp;quot;инсталляция ruby&amp;quot; остаётся в системе со всеми своими &amp;quot;устаревшими&amp;quot; и &amp;quot;уязвимыми&amp;quot; версиями. И эти версии очень нравятся сканерам для того, чтобы поднять тревогу. &lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;1PEj&quot;&gt;И еще два слова о том, что же именно сканирует Trivy (касательно ruby конечно):&lt;/p&gt;
  &lt;ul id=&quot;VCc7&quot;&gt;
    &lt;li id=&quot;SPfK&quot;&gt;сканирует &lt;a href=&quot;https://guides.rubygems.org/specification-reference/&quot; target=&quot;_blank&quot;&gt;.gemspec файлы&lt;/a&gt; на всей файловой системе и не важно, установлен гем или нет - если в .gemspec указана версия &amp;quot;с уязвимостью&amp;quot; - алярм;&lt;/li&gt;
    &lt;li id=&quot;aYT7&quot;&gt;сканирует .gem файлы на всей файловой системе и не важно, установлен гем, лежит просто в папке cache, используется или нет в вашем приложении.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;WVkR&quot;&gt;Значит для удовлетворения хотелок сканера, надо сделать так, чтоб нигде не лежало ничего лишнего или не используемого, &lt;strong&gt;включая&lt;/strong&gt; &lt;a href=&quot;https://docs.ruby-lang.org/en/3.2/standard_library_rdoc.html&quot; target=&quot;_blank&quot;&gt;stdlib&lt;/a&gt; - стандартную библиотеку ruby.&lt;/p&gt;
  &lt;p id=&quot;O7U3&quot;&gt;Проблема ясна, задача очевидна - можно приступать к решению и начинать надо с базовых образов. Их надо собрать так, чтобы сами базовые образы уже не содержали никаких уязвимостей.&lt;/p&gt;
  &lt;figure id=&quot;5sG8&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/e1/7a/e17a3417-a64b-4027-adf2-971f332cf9dd.png&quot; width=&quot;1341&quot; /&gt;
    &lt;figcaption&gt;Немного удручающее зрелище, особенно для ИБ клиента&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;435p&quot;&gt;Что такое инсталляция ruby?&lt;/h2&gt;
  &lt;p id=&quot;inA8&quot;&gt;Если мы возьмём любой дистрибутив с установленным там ruby, то увидим, что само ruby будет находиться где-то в районе:&lt;/p&gt;
  &lt;ul id=&quot;2S4M&quot;&gt;
    &lt;li id=&quot;4NT4&quot;&gt;/usr/lib/ruby - например в alpine после &lt;code&gt;apk add ruby&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;P9jW&quot;&gt;/usr/local/lib/ruby - например в &lt;code&gt;ruby:alpine&lt;/code&gt;, где руби собирается отдельно от apk&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;VPEZ&quot;&gt;А дальше начинается интересное. Если посмотреть в финальный образ, в котором сделано много различных операций (обновление ruby, установка гемов через &lt;code&gt;gem install&lt;/code&gt;, установка через &lt;code&gt;bundle install&lt;/code&gt;), то в корневой папке ruby обнаружится несколько папок, по которым тем или иным способом будут распределены установленные вами гемы:&lt;/p&gt;
  &lt;pre id=&quot;qpVC&quot;&gt;/usr/local/lib/ruby
├── 3.2.0
├── gems/3.2.0
├── site_ruby/3.2.0
└── vendor_ruby/3.2.0&lt;/pre&gt;
  &lt;p id=&quot;v764&quot;&gt;Сразу добавим к этому списку другие папки (которые можно посмотреть в выводе команды &lt;code&gt;gem env&lt;/code&gt;):&lt;/p&gt;
  &lt;pre id=&quot;8elp&quot; data-lang=&quot;yaml&quot;&gt; - INSTALLATION DIRECTORY: /usr/local/bundle
 - USER INSTALLATION DIRECTORY: /root/.local/share/gem/ruby/3.3.0
 - SPEC CACHE DIRECTORY: /root/.cache/gem/specs
 - GEM PATHS:
    - /usr/lib/ruby/gems/3.3.0
    - /root/.local/share/gem/ruby/3.3.0&lt;/pre&gt;
  &lt;p id=&quot;LdQo&quot;&gt;И вишенкой на торте будет настройка вашего bundle, если вы используете кеширование сборки (&lt;a href=&quot;https://bundler.io/man/bundle-cache.1.html&quot; target=&quot;_blank&quot;&gt;bundle cache&lt;/a&gt; или &lt;code&gt;bundle config set cache_path vendor/cache&lt;/code&gt;), и используемые в этом зоопарке переменные окружения &lt;code&gt;GEM_HOME&lt;/code&gt;, &lt;code&gt;BUNDLE_CACHE_PATH&lt;/code&gt;, &lt;code&gt;BUNDLE_PATH&lt;/code&gt; и скорее всего еще какие-то скрытые в глубинах экосистемы ruby.&lt;/p&gt;
  &lt;p id=&quot;0kPd&quot;&gt;Немного путано и в результате беспорядочных &lt;s&gt;связей&lt;/s&gt; установок и обновлений во всех этих папках могут (и будут) появляться гемы. Надо исправить!&lt;/p&gt;
  &lt;p id=&quot;fK8k&quot;&gt;Постараюсь дать верхнеуровневое описание, что же именно это за папочки, не углубляясь в подробности и исключения:&lt;/p&gt;
  &lt;h3 id=&quot;4n1D&quot;&gt;/usr/local/lib/ruby/3.3.0&lt;/h3&gt;
  &lt;p id=&quot;VUFG&quot;&gt;Именно в этой папке установлены основные файлы ruby и stdlib, а также компилируемые расширения в папке x86_64-linux-musl (в нашем случае собранные alpine для x86_64). Эти файлы принадлежат условной &amp;quot;системе&amp;quot; и никак не будут изменяться при дальнейших модификациях, например при установке или обновлении гемов через &lt;code&gt;gem install&lt;/code&gt;. Вместо этого библиотеки будут ставиться в папку &lt;code&gt;/usr/local/lib/ruby/gems/3.3.0&lt;/code&gt;&lt;/p&gt;
  &lt;h3 id=&quot;C77G&quot;&gt;/usr/local/lib/ruby/gems/3.3.0&lt;/h3&gt;
  &lt;p id=&quot;jTUW&quot;&gt;Тут собрано всё, что вы ставите (без модификации &lt;code&gt;GEM_PATH&lt;/code&gt;) командами &lt;code&gt;gem install,&lt;/code&gt; включая компилируемые расширения.&lt;/p&gt;
  &lt;h3 id=&quot;JY0S&quot;&gt;/usr/local/lib/ruby/vendor_ruby/3.3.0&lt;/h3&gt;
  &lt;p id=&quot;Gh6c&quot;&gt;Сюда &lt;strong&gt;должны&lt;/strong&gt; ставиться дополнительные гемы и/или патчи от команды мейнтейнеров дистрибутива. Об этом сложно найти информацию, но несколько слов есть в книге &lt;code&gt;The Ruby Programming Language&lt;/code&gt; или, что интереснее, в changelog для &lt;a href=&quot;https://docs.ruby-lang.org/en/2.3.0/NEWS-1_8_7.html&quot; target=&quot;_blank&quot;&gt;NEWS for Ruby 1.8.7&lt;/a&gt; (да, окаменелое…):&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;VAZR&quot;&gt;&lt;strong&gt;vendor_ruby&lt;/strong&gt; directory&lt;br /&gt;A new library directory named &lt;code&gt;vendor_ruby&lt;/code&gt; is introduced in addition to &lt;code&gt;site_ruby&lt;/code&gt;. The idea is to separate libraries installed by the package system (&lt;code&gt;vendor&lt;/code&gt;) from manually (&lt;code&gt;site&lt;/code&gt;) installed libraries preventing the former from getting overwritten by the latter, while preserving the user option to override vendor libraries with site libraries. (&lt;code&gt;site_ruby&lt;/code&gt; takes precedence over &lt;code&gt;vendor_ruby&lt;/code&gt;)&lt;br /&gt;If you are a package maintainer, make each library package configure the library passing the &lt;code&gt;--vendor&lt;/code&gt; option to &lt;code&gt;extconf.rb&lt;/code&gt; so that the library files will get installed under &lt;code&gt;vendor_ruby&lt;/code&gt;.&lt;br /&gt;You can change the directory locations using configure options such as&lt;code&gt; --with-sitedir=DIR&lt;/code&gt; and &lt;code&gt;--with-vendordir=DIR&lt;/code&gt;.&lt;/p&gt;
  &lt;/section&gt;
  &lt;h3 id=&quot;gcRG&quot;&gt;/usr/local/lib/ruby/site_ruby/3.3.0&lt;/h3&gt;
  &lt;p id=&quot;R2OC&quot;&gt;Сюда будут ставиться &amp;quot;системные&amp;quot;  файлы, но не от OS, а от самого ruby, например после выполнения &lt;code&gt;gem update –system&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;6S3o&quot;&gt;site_ruby/
└── 3.4.0
   ├── bundler
   ├── rubygems
   └── x86_64-linux-musl&lt;/pre&gt;
  &lt;h3 id=&quot;YxNI&quot;&gt;/usr/local/lib/ruby/gems/3.3.0/specifications/&lt;/h3&gt;
  &lt;p id=&quot;1lAC&quot;&gt;…а также INSTALLATION DIRECTORY &lt;code&gt;/usr/local/bundle/specifications&lt;/code&gt;…&lt;br /&gt;…а также USER INSTALLATION DIRECTORY: &lt;code&gt;/root/.local/share/gem/ruby/3.3.0&lt;/code&gt;…&lt;br /&gt;…а также SPEC CACHE DIRECTORY &lt;code&gt;/root/.cache/gem/specs&lt;/code&gt;…&lt;br /&gt;…а также BUNDLE_CACHE_PATH …&lt;/p&gt;
  &lt;p id=&quot;0Kdg&quot;&gt;Сюда ставятся .gemspec файлы, которые собственно и говорят пакетному менеджеру ruby (gem и bundle), какие именно версии каких гемов установлены. &lt;/p&gt;
  &lt;h3 id=&quot;fjpv&quot;&gt;/usr/local/lib/ruby/gems/3.3.0/specifications/default&lt;/h3&gt;
  &lt;p id=&quot;MBdG&quot;&gt;Default, Карл… Default - это особое состояние гема, и эти гемы нельзя удалить. Если вы обновили гем до новой версии, то старая всё равно останется, и Trivy вам этого не простит. Весьма вредная папка с точки зрения сканирования.&lt;/p&gt;
  &lt;h3 id=&quot;OzUZ&quot;&gt;/usr/local/lib/ruby/gems/3.3.0/cache/&lt;/h3&gt;
  &lt;p id=&quot;eGmS&quot;&gt;…а также INSTALLATION DIRECTORY &lt;code&gt;/usr/local/bundle/cache&lt;/code&gt;…&lt;br /&gt;…а также BUNDLE_PATH …&lt;/p&gt;
  &lt;p id=&quot;Khcn&quot;&gt;Сюда пакетный менеджер ruby (gem или bundle) скачивает гемы (допустим вы ставите faraday.gem) перед установкой. Этот кеш очень часто используется для ускорения сборки, например &lt;a href=&quot;https://docs.gitlab.com/ee/ci/caching/#cache-ruby-dependencies&quot; target=&quot;_blank&quot;&gt;в вашем Gitlab&lt;/a&gt;.&lt;/p&gt;
  &lt;h2 id=&quot;WrYc&quot;&gt;Что здесь у вас происходит?!&lt;/h2&gt;
  &lt;p id=&quot;5S1n&quot;&gt;После того как мы в процессе исследования детально разобрались и увидели всё это многообразие мест, где могут находиться файлы, вызывающие панические атаки у Trivy, мы решили радикально решить эту проблему: свести все файлы, все гемы и все спеки (.gemspec) в одно место. Это позволит легко следить за всеми (всеми!) фактическими зависимостями и эффективно пользоваться командами &lt;code&gt;gem cleanup&lt;/code&gt; и &lt;code&gt;bundle clean&lt;/code&gt;. Тут надо отметить, что для вашей рабочей OS (системы общего назначения) такое решение приведёт к поломке системного пакетного менеджера (&lt;a href=&quot;https://ru.wikipedia.org/wiki/Portage&quot; target=&quot;_blank&quot;&gt;Gentoo Portage&lt;/a&gt;, &lt;a href=&quot;https://ru.wikipedia.org/wiki/Dpkg&quot; target=&quot;_blank&quot;&gt;dpkg/apt&lt;/a&gt;, &lt;a href=&quot;https://ru.wikipedia.org/wiki/RPM&quot; target=&quot;_blank&quot;&gt;rpm/zypper&lt;/a&gt; и пр.), но мы ведь говорим о конкретной сборке ruby под ваш конкретный проект - и тут никаких проблем не будет.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;ma0i&quot;&gt;Для того чтобы узнать что куда, когда и зачем ставится, мы использовали git прямо на корне файловой системы внутри контейнера (ruby:alpine, просто alpine, ruby:debian и другие образы для сравнения) и фиксировали изменения после различных команд ⏳&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;yFJE&quot;&gt;Но это еще не всё. Остаётся еще проблема с default gemspec, но её относительно легко решить:&lt;/p&gt;
  &lt;pre id=&quot;BNDO&quot; data-lang=&quot;bash&quot;&gt;mv /usr/local/lib/ruby/gems/3.3.0/specifications/default/* /usr/local/lib/ruby/gems/3.3.0/specifications/&lt;/pre&gt;
  &lt;p id=&quot;cyty&quot;&gt;Теперь мы можем сформулировать План:&lt;/p&gt;
  &lt;ul id=&quot;Cw2d&quot;&gt;
    &lt;li id=&quot;SFp4&quot;&gt;Все дороги ведут в Рим - делаем ссылочки для &lt;code&gt;vendor_ruby&lt;/code&gt;, &lt;code&gt;site_ruby&lt;/code&gt; и пр. Также явно прописываем системные переменные &lt;code&gt;GEM_HOME&lt;/code&gt; и &lt;code&gt;BUNDLE_APP_CONFIG.&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;f5A9&quot;&gt;Разбираемся с default gems.&lt;/li&gt;
    &lt;li id=&quot;WLtx&quot;&gt;Обновляем ruby (имеется в виду stdlib) до последней требуемой версии.&lt;/li&gt;
    &lt;li id=&quot;jxt0&quot;&gt;Обновляем bundle до нужной версии.&lt;/li&gt;
    &lt;li id=&quot;PMlW&quot;&gt;Удаляем лишние гемы (например rdoc).&lt;/li&gt;
    &lt;li id=&quot;bnle&quot;&gt;Переставляем (!) системные (на текущий момент сборки базового образа -  &lt;strong&gt;все&lt;/strong&gt;) гемы, потому что, как оказалось, &lt;code&gt;mv&lt;/code&gt; для default gemspec имеет не очень хорошие последствия.&lt;/li&gt;
    &lt;li id=&quot;Ltwp&quot;&gt;profit!&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;DVti&quot;&gt;Сказано - сделано, и добро пожаловать под кат!&lt;/p&gt;
  &lt;pre id=&quot;6e0L&quot; data-lang=&quot;dockerfile&quot;&gt;ARG BASE_RUBY=3.2
ARG BASE_ALPINE=alpine3.16
ARG BASE_IMAGE=ruby:${BASE_RUBY}-${BASE_ALPINE}

FROM ${BASE_IMAGE}

ARG BASE_RUBY=3.2

ARG RUBYGEMS_VERSION=3.5.20
ARG BUNDLER_VERSION=2.5.20

# эта переменная есть в старом alpine но нет в debian и новом
# добавляем потому что она очень нужна для работы с папочками
ENV RUBY_MAJOR=${BASE_RUBY}

ENV RUBYGEMS_VERSION=${RUBYGEMS_VERSION} \
    BUNDLER_VERSION=${BUNDLER_VERSION}

RUN apk update &amp;amp;&amp;amp; apk upgrade

# Это наш костыльный скрипт который удаляет всякие лишние кеши,
# man-файлы и прочий мусор. 
COPY common/scripts/cleanallbuilds.sh /usr/bin/

# dumb-init всегда используем как PID-1 но это немного другая история
RUN set -ex \
 &amp;amp;&amp;amp; apk add dumb-init \
 &amp;amp;&amp;amp; cleanallbuilds.sh

###### 
# Надругиваемся над диструбутивом, чтоб иметь строго
# одну версию руби в систему и управлять ею целиком через gem/bundle

# все пути ведут в Рим
ENV GEM_HOME=/usr/local/lib/ruby/gems/${RUBY_MAJOR}.0/
ENV BUNDLE_APP_CONFIG=/usr/local/lib/ruby/gems/${RUBY_MAJOR}.0/

# Сводим vendor_ruby, site_ruby и GEM_HOME в одно место
RUN set -ex \
 &amp;amp;&amp;amp; rm -rf /usr/local/lib/ruby/site_ruby /usr/local/lib/ruby/vendor_ruby \
 &amp;amp;&amp;amp; ln -sf /usr/local/lib/ruby /usr/local/lib/ruby/site_ruby \
 &amp;amp;&amp;amp; ln -sf /usr/local/lib/ruby /usr/local/lib/ruby/vendor_ruby \
 &amp;amp;&amp;amp; mkdir -p /root/.local/share/gem/ruby/ \
 &amp;amp;&amp;amp; ln -sf ${GEM_HOME} /root/.local/share/gem/ruby/${RUBY_MAJOR}.0

# Чутка тюним bundle config чтоб в дальнейшем не забыть
RUN set -ex \
 &amp;amp;&amp;amp; bundle config --local disable_version_check true \
 &amp;amp;&amp;amp; bundle config --local clean false \
 &amp;amp;&amp;amp; bundle config --local no_prune false \
 &amp;amp;&amp;amp; bundle config --local disable_local_branch_check true \
 &amp;amp;&amp;amp; bundle config --local jobs 2 \
 &amp;amp;&amp;amp; bundle config --local allow_offline_install true

# настраиваем .gemrc чтоб не было ничего лишнего, вклюячая rdoc
RUN set -ex \
 &amp;amp;&amp;amp; echo &amp;#x27;gem: --no-document&amp;#x27; &amp;gt; /usr/local/etc/gemrc \
 &amp;amp;&amp;amp; echo &amp;#x27;update_sources: false&amp;#x27; &amp;gt;&amp;gt; /usr/local/etc/gemrc \
 &amp;amp;&amp;amp; echo &amp;#x27;verbose: false&amp;#x27; &amp;gt;&amp;gt; /usr/local/etc/gemrc \
 &amp;amp;&amp;amp; echo &amp;#x27;update: --no-suggestions&amp;#x27; &amp;gt;&amp;gt; /usr/local/etc/gemrc \
 &amp;amp;&amp;amp; echo &amp;#x27;install: --no-suggestions --conservative&amp;#x27; &amp;gt;&amp;gt; /usr/local/etc/gemrc

# пытаемся удалить ненужные гемы с самого начала - попытка не пытка
RUN set -ex \
 &amp;amp;&amp;amp; gem uninstall -a -x --quiet --force &amp;#x60;gem list | cut -f 1 -d &amp;quot; &amp;quot;&amp;#x60; \
 &amp;amp;&amp;amp; gem cleanup

RUN set -ex \
 &amp;amp;&amp;amp; apk add --virtual .build-deps \
   autoconf \
   bison \
   bzip2 \
   bzip2-dev \
   coreutils \
   curl-dev \
   dpkg-dev dpkg \
   g++ \
   gcc \
   gdbm-dev \
   git \
   glib-dev \
   libc-dev \
   libffi-dev \
   libxml2-dev \
   libxslt-dev \
   linux-headers \
   make \
   ncurses-dev \
   procps \
   readline-dev \
   tar \
   xz \
   yaml-dev \
   zlib-dev \
   shared-mime-info \
 &amp;amp;&amp;amp; mv /usr/local/lib/ruby/gems/${RUBY_MAJOR}.0/specifications/default/* /usr/local/lib/ruby/gems/${RUBY_MAJOR}.0/specifications/ \
 &amp;amp;&amp;amp; gem cleanup \
 &amp;amp;&amp;amp; gem update --system &amp;quot;${RUBYGEMS_VERSION}&amp;quot; \
 &amp;amp;&amp;amp; gem uninstall bundler --all --silent || true \
 &amp;amp;&amp;amp; gem install bundler -v &amp;quot;${BUNDLER_VERSION}&amp;quot; \
 &amp;amp;&amp;amp; gem uninstall rdoc --all --silent || true \
 &amp;amp;&amp;amp; GMS=&amp;#x60;gem list | sed s/default:\ // | sed -E &amp;#x27;s/\ \((.*)\)/:\1/&amp;#x27; | sort&amp;#x60; \
 &amp;amp;&amp;amp; DEBUG_FLAGS=&amp;quot;-Wno-calloc-transposed-args&amp;quot; gem pristine --all --extensions \
 &amp;amp;&amp;amp; commands=$(for x in $GMS; do \
   g=$(echo &amp;quot;$x&amp;quot; | cut -f 1 -d &amp;quot;:&amp;quot;); \
   v=$(echo &amp;quot;$x&amp;quot; | cut -f 2 -d &amp;quot;:&amp;quot;); \
   echo gem pristine $g --version &amp;quot;$v&amp;quot;; \
 done) \
 &amp;amp;&amp;amp; echo -e &amp;quot;$commands&amp;quot; | xargs -I CMD -P 3 bash -c CMD\
 &amp;amp;&amp;amp; gem cleanup \
 &amp;amp;&amp;amp; rm -rf root/.local/share/gem/specs \
 &amp;amp;&amp;amp; apk del .build-deps \
 &amp;amp;&amp;amp; cleanallbuilds.sh

WORKDIR /home/app
RUN set -ex \
 &amp;amp;&amp;amp; adduser -D -s /sbin/nologin app \
 &amp;amp;&amp;amp; chown -R app:app /home/app

ENTRYPOINT [&amp;quot;/usr/bin/entrypoint.sh&amp;quot;]
SHELL   [&amp;quot;/bin/sh&amp;quot;, &amp;quot;-c&amp;quot;] &lt;/pre&gt;
  &lt;p id=&quot;Jtm9&quot;&gt;Самое интересное, конечно, находится в одном слое самого пухленького RUN, и по некоторым командам надо дать пояснения:&lt;/p&gt;
  &lt;ul id=&quot;W4Qg&quot;&gt;
    &lt;li id=&quot;U30m&quot;&gt;&lt;code&gt;GMS=&amp;#x60;gem list | sed s/default:\ // | sed -E &amp;#x27;s/\ \((.*)\)/:\1/&amp;#x27; | sort&amp;#x60;&lt;/code&gt; - получаем список установленных гемов с версиями в формате &amp;quot;yaml:0.4.0 zlib:3.2.1&amp;quot;. Это нам потребуется дальше из-за странного поведения &lt;code&gt;gem pristine&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;nqbu&quot;&gt;&lt;code&gt;gem pristine --all --extensions&lt;/code&gt; - должен для всех установленных гемов сделать &amp;quot;чистовую&amp;quot; установку, включая скачивание и перекомпиляцию расширений. Но нет. На самом деле он делает не для всех и не всё :(&lt;/li&gt;
    &lt;li id=&quot;LeLj&quot;&gt;&lt;code&gt;DEBUG_FLAGS=&amp;quot;-Wno-calloc-transposed-args&amp;quot;&lt;/code&gt; - мы используем один Dockerfile для сборки базовых образов ruby 2.7, 3.0, 3.1, 3.2, и по умолчанию все расширения должны компилироваться без единого &lt;s&gt;разрыва&lt;/s&gt; ворнинга компиляции, но для некоторых версий руби (кажется 3.0) это не так, и именно этот ворнинг всё портит, поэтому его подавляем без затей.&lt;/li&gt;
    &lt;li id=&quot;XzSy&quot;&gt;&lt;code&gt;commands=$(for x in $GMS; do …&lt;/code&gt; - а вы знали, что в bash тоже есть пул потоков? Строго говоря, он &lt;a href=&quot;https://stackoverflow.com/questions/6441509/how-to-write-a-process-pool-bash-shell&quot; target=&quot;_blank&quot;&gt;пул процессов&lt;/a&gt;, и не в баше, а в xargs, но это не важно. В общем тут список &lt;code&gt;$GMS&lt;/code&gt; в виде &amp;quot;yaml:0.4.0 zlib:3.2.1&amp;quot; трансформируется в список &lt;code&gt;$commands&lt;/code&gt;, потому что в такой форме &lt;code&gt;gem pristine&lt;/code&gt; работает именно так как надо:&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;BIHe&quot;&gt;gem pristine yaml --version &amp;quot;0.4.0&amp;quot;
gem pristine zlib --version &amp;quot;3.2.1&amp;quot;
...&lt;/pre&gt;
  &lt;ul id=&quot;Zo9z&quot;&gt;
    &lt;li id=&quot;nISI&quot;&gt;&lt;code&gt;echo -e &amp;quot;$commands&amp;quot; | xargs -I CMD -P 3 bash -c CMD&lt;/code&gt; - отправляем &lt;code&gt;$commands&lt;/code&gt; в пул из трех процессов для одновременной установки гемов. &lt;/li&gt;
    &lt;li id=&quot;x3Ms&quot;&gt;&lt;code&gt;cleanallbuilds.sh&lt;/code&gt; - зовём в самом конце кустарный скрипт, который удаляет всякий мусор из системы:&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;BmX3&quot; data-lang=&quot;shell&quot;&gt;#!/bin/sh

# ruby (alpine + debian)
# Скрипт единый идля debian и для alpine, поэтому ошибки,
# возникающие из-за разницы дистрибутивов просто игнорируем
rm -rf /usr/src/ruby/ || true
rm -rf /root/.local/share/gem/specs/ || true
rm -rf /root/.local/state/ || true
rm -rf /root/.cache/gem/ || true
rm -rf /usr/local/lib/ruby/gems/3.2.0/cache/ || true
rm -rf /root/.bundle/cache/ || true
gem cleanup || true

# alpine
apk cache clean || true

# debian
apt-get clean autoclean || true
apt-get autoclean --yes || true
apt-get autoremove --yes || true

find /var/log/ -type f -delete || true
find /var/lib/log/ -type f -delete || true
find /usr/share/doc/ -type f -delete || true&lt;/pre&gt;
  &lt;h2 id=&quot;ri1b&quot;&gt;Результат&lt;/h2&gt;
  &lt;p id=&quot;evYm&quot;&gt;Старый образ занимал 900MB, новый - 152MB (не спрашивайте...)&lt;/p&gt;
  &lt;figure id=&quot;1v7h&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c3/95/c395d60f-18a0-45a0-9757-f7b7f779949c.png&quot; width=&quot;1071&quot; /&gt;
    &lt;figcaption&gt;Значительно лучше. Особенно для базового образа&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;XWm5&quot;&gt;Что дальше?&lt;/h2&gt;
  &lt;p id=&quot;wdWN&quot;&gt;Теперь эти базовые образы надо начать использовать непосредственно в CI пайплайнах боевых сервисов и, конечно, процедуру сборки и тестирования придётся поменять. Точнее, мы уже перешли на новые образы и сборку уже пару недель назад - дело теперь только за статьёй. А в качестве приманки приведу результаты перехода для одного из самых &amp;quot;толстых&amp;quot; наших сервисов:&lt;/p&gt;
  &lt;pre id=&quot;U2Zj&quot;&gt;image:old       size:1.49GB   vulns:255
image:new-prod  size:259MB    vulns:1 (Вчерашняя!! СVE-2025-25186)
image:new-test  size:269MB    vulns:1 (СVE-2025-25186)&lt;/pre&gt;
  &lt;p id=&quot;LyAS&quot;&gt;Если взглянуть ретроспективно, то совсем не весело - так что:&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;tf2v&quot;&gt;профилируйте чаще не только ваш код и инфраструктуру сборки.&lt;/p&gt;
  &lt;/section&gt;
  &lt;tt-tags id=&quot;HMo0&quot;&gt;
    &lt;tt-tag name=&quot;docker&quot;&gt;#docker&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;devsecops&quot;&gt;#devsecops&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;ruby&quot;&gt;#ruby&lt;/tt-tag&gt;
    &lt;tt-tag name=&quot;cicd&quot;&gt;#cicd&lt;/tt-tag&gt;
  &lt;/tt-tags&gt;

</content></entry><entry><id>rnds:063-selenium</id><link rel="alternate" type="text/html" href="https://blog.rnds.pro/063-selenium?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rnds"></link><title>Selenium. Как заставить браузер работать на вас</title><published>2024-12-25T05:52:13.522Z</published><updated>2025-01-13T11:56:20.550Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/72/d4/72d44c8b-619f-4719-8b9f-3309dbb9b589.png"></media:thumbnail><category term="other" label="other"></category><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/ba/29/ba29aa5e-fa6b-4c40-96c7-8a0369fcce13.jpeg&quot;&gt;Всем привет! Сегодня я расскажу что такое Selenium, как его запустить, зачем он нужен и какие у него плюсы и минусы есть.</summary><content type="html">
  &lt;figure id=&quot;GPrC&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/ba/29/ba29aa5e-fa6b-4c40-96c7-8a0369fcce13.jpeg&quot; width=&quot;1043&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;zdTz&quot;&gt;Всем привет! Сегодня я расскажу, что такое Selenium, как его запустить, зачем он нужен, и какие у него есть плюсы и минусы.&lt;/p&gt;
  &lt;h2 id=&quot;hrbZ&quot;&gt;Небольшая вводная&lt;/h2&gt;
  &lt;p id=&quot;0LMg&quot;&gt;Selenium — это инструмент для автоматизации веб-браузеров. Он позволяет разработчикам и тестировщикам писать скрипты, которые могут управлять браузером, имитируя действия пользователя, такие как клики, ввод текста и навигация по страницам.&lt;/p&gt;
  &lt;p id=&quot;aouS&quot;&gt;Selenium поддерживает множество языков программирования, включая Python, Java, Ruby и другие, и может работать с различными браузерам. Это делает его популярным выбором для автоматизации тестирования веб-приложений и выполнения рутинных задач в браузере.&lt;/p&gt;
  &lt;h2 id=&quot;hQ2s&quot;&gt;Задачи, решаемые Selenium&lt;/h2&gt;
  &lt;p id=&quot;o4NF&quot;&gt;Можно разделить на 2 большие группы. Это тестирование и парсинг (скрейпинг). Парсинг - это процесс извлечения и обработки данных из целевых ресурсов.&lt;/p&gt;
  &lt;p id=&quot;Kf71&quot;&gt;К тестированию можно отнести такие подгруппы как:&lt;/p&gt;
  &lt;ul id=&quot;o3xV&quot;&gt;
    &lt;li id=&quot;cxwZ&quot;&gt;&lt;strong&gt;Тестирование пользовательских интерфейсов.&lt;/strong&gt; Позволяет проверять элементы интерфейса, такие как кнопки, поля ввода и ссылки, чтобы убедиться, что они работают, как задумано.&lt;/li&gt;
    &lt;li id=&quot;BtkL&quot;&gt;&lt;strong&gt;Кросс-браузерное тестирование.&lt;/strong&gt; Позволяет запускать тесты в различных браузерах, что помогает убедиться, что приложение работает корректно в разных средах.&lt;/li&gt;
    &lt;li id=&quot;6v4o&quot;&gt;&lt;strong&gt;Регрессионное тестирование.&lt;/strong&gt; Позволяет повторно запускать тесты после внесения изменений в код, чтобы убедиться, что новые изменения не нарушили существующий функционал.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;9eXE&quot;&gt;В простых случаях можно обойтись и без Selenium. Например получать html страницы с помощью простых инструментов, таких как curl.&lt;/p&gt;
  &lt;p id=&quot;lWwN&quot;&gt;Но бывает более сложные случаи, когда Selenium становится нашим лучшим другом:&lt;/p&gt;
  &lt;ul id=&quot;2BCV&quot;&gt;
    &lt;li id=&quot;Mg7s&quot;&gt;&lt;strong&gt;Динамически загружаемые страницы.&lt;/strong&gt; SPA приложению требуется js.&lt;/li&gt;
    &lt;li id=&quot;lNeM&quot;&gt;&lt;strong&gt;Обход ограничений.&lt;/strong&gt; Современные сайты часто содержат механизмы защиты от парсинга. Например, проверка cookie, user-agent и, конечно же, captcha.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;1AMm&quot;&gt;&lt;em&gt;Для обхода капчи часто используется комбинация Selenium, который позволяет выполнять js на странице и прогрузить капчу, и специального механизма для решения капчи. К таким механизмам относятся:&lt;/em&gt;&lt;/p&gt;
  &lt;ul id=&quot;psD7&quot;&gt;
    &lt;li id=&quot;GmE0&quot;&gt;&lt;em&gt;&lt;strong&gt;Сторонние платные сервисы.&lt;/strong&gt; Принимают изображение или аудиофайл через API и возвращают готовое решение.&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;EC3f&quot;&gt;&lt;em&gt;&lt;strong&gt;Обученные нейронные сети.&lt;/strong&gt; Запускаются локально и самостоятельно распознают капчи.&lt;/em&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;fv4F&quot;&gt;&lt;em&gt;После получения ответа от выбранного механизма Selenium используется для ввода решения в поле или для выполнения требуемых действий, например, выбора объектов вроде &amp;quot;светофоров&amp;quot; или “мостов”.&lt;/em&gt;&lt;/p&gt;
  &lt;h2 id=&quot;4GFQ&quot;&gt;Конфигурирование приложения для работы с Selenium&lt;/h2&gt;
  &lt;p id=&quot;f4d0&quot;&gt;Рассмотрим конфигурирование на примере Ruby on Rails приложения.&lt;/p&gt;
  &lt;p id=&quot;ZEic&quot;&gt;В первую очередь нам необходимо поставить гемы. Для этого добавляем в Gemfile следующие строки:&lt;/p&gt;
  &lt;pre id=&quot;PI4W&quot; data-lang=&quot;ruby&quot;&gt;gem &amp;#x27;selenium-webdriver&amp;#x27;
gem &amp;#x27;webdrivers&amp;#x27;&lt;/pre&gt;
  &lt;p id=&quot;UZnW&quot;&gt;И устанавливаем гемы:&lt;/p&gt;
  &lt;pre id=&quot;5VFW&quot; data-lang=&quot;bash&quot;&gt;bundle install&lt;/pre&gt;
  &lt;h2 id=&quot;1ipk&quot;&gt;Работа с Selenium&lt;/h2&gt;
  &lt;p id=&quot;VB5K&quot;&gt;Пример парсинга данных:&lt;/p&gt;
  &lt;pre id=&quot;K0H1&quot; data-lang=&quot;ruby&quot;&gt;require &amp;#x27;selenium-webdriver&amp;#x27;
require &amp;#x27;webdrivers&amp;#x27;

driver = Selenium::WebDriver.for :chrome
driver.navigate.to &amp;#x27;http://example.com/products&amp;#x27;
products = driver.find_elements(class: &amp;#x27;product-item&amp;#x27;)

products.each do |product|
  name = product.find_element(class: &amp;#x27;product-name&amp;#x27;).text
  price = product.find_element(class: &amp;#x27;product-price&amp;#x27;).text
  Rails.logger.info { &amp;quot;Название: #{name}, Цена: #{price}&amp;quot; }
end

driver.quit
&lt;/pre&gt;
  &lt;p id=&quot;ofp0&quot;&gt;Функционально пример выше найдет названия и цену товаров на воображаемом сайте и напечатает их в логах. Но этот код можно улучшить.&lt;/p&gt;
  &lt;p id=&quot;Z08r&quot;&gt;Во-первых, нужно учитывать то, что Selenium - это браузер, а значит нам стоит ждать загрузки страницы. Поэтому модифицируем код, и при попытке получить список товаров, ждем 5 секунд и только потом падаем с ошибкой.&lt;/p&gt;
  &lt;pre id=&quot;QTSO&quot; data-lang=&quot;ruby&quot;&gt;products = Selenium::WebDriver::Wait.new(timeout: 5).until do
  driver.find_elements(class: &amp;#x27;product-item&amp;#x27;)
end&lt;/pre&gt;
  &lt;p id=&quot;i6bm&quot;&gt;Во-вторых, нужно хоть немного замаскироваться от систем сайта, которые ограничивают работу парсеров. Для этого добавим немного “человечности” нашему браузеру.&lt;/p&gt;
  &lt;pre id=&quot;z260&quot; data-lang=&quot;ruby&quot;&gt;options = Selenium::WebDriver::Chrome::Options.new
options.add_argument(&amp;#x27;--disable-blink-features=AutomationControlled&amp;#x27;)
options.add_argument(&amp;#x27;--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36&amp;#x27;)
options.add_argument(&amp;#x27;--headless=new&amp;#x27;)
options.add_argument(&amp;#x27;--disable-gpu&amp;#x27;)
options.add_argument(&amp;#x27;window-size=1200x800&amp;#x27;)

driver = Selenium::WebDriver.for(:chrome, options: options)&lt;/pre&gt;
  &lt;p id=&quot;1Hci&quot;&gt;Небольшое пояснение к опциям:&lt;/p&gt;
  &lt;ul id=&quot;bIGo&quot;&gt;
    &lt;li id=&quot;XgnE&quot;&gt;&lt;strong&gt;--disable-blink-features=AutomationControlled.&lt;/strong&gt; Отключает некоторые функции Blink, которые указывают на то, что браузер управляется автоматизированным инструментом. Может помочь избежать обнаружения автоматизации на некоторых сайтах.&lt;/li&gt;
    &lt;li id=&quot;gQJR&quot;&gt;&lt;strong&gt;--user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36.&lt;/strong&gt; Устанавливает пользовательский агент (User-Agent) для браузера. Пользовательский агент сообщает веб-сайтам, какую версию браузера и операционной системы использует пользователь. Установка пользовательского агента может помочь в обходе блокировок или в получении контента, оптимизированного для определенного браузера.&lt;/li&gt;
    &lt;li id=&quot;BAbJ&quot;&gt;&lt;strong&gt;--headless=new.&lt;/strong&gt; Запускает браузер в &amp;quot;безголовом&amp;quot; режиме, что означает, что он будет работать без графического интерфейса. Нужно для автоматизации и тестирования.&lt;/li&gt;
    &lt;li id=&quot;3Nkc&quot;&gt;&lt;strong&gt;--disable-gpu.&lt;/strong&gt; Отключает использование графического процессора (GPU). Полезно в безголовом режиме, так как некоторые функции, зависящие от GPU, могут вызывать проблемы или не поддерживаться.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;dksd&quot;&gt;По итогу получится такой код:&lt;/p&gt;
  &lt;pre id=&quot;QQfj&quot; data-lang=&quot;ruby&quot;&gt;require &amp;#x27;selenium-webdriver&amp;#x27;
require &amp;#x27;webdrivers&amp;#x27;

def wait(time)
  Selenium::WebDriver::Wait.new(timeout: time)
end

def parse
  options = Selenium::WebDriver::Chrome::Options.new
  options.add_argument(&amp;#x27;--disable-blink-features=AutomationControlled&amp;#x27;)
  options.add_argument(&amp;#x27;--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36&amp;#x27;)
  options.add_argument(&amp;#x27;--headless=new&amp;#x27;)
  options.add_argument(&amp;#x27;--disable-gpu&amp;#x27;)

  driver = Selenium::WebDriver.for(:chrome, options: options)

  driver.navigate.to &amp;#x27;https://example.com/products&amp;#x27;
  products = wait(5).until { driver.find_elements(class: &amp;#x27;product-item&amp;#x27;) }
  products.each do |product|
    name = product.find_element(class: &amp;#x27;product-name&amp;#x27;).text
    price = product.find_element(class: &amp;#x27;product-price&amp;#x27;).text
    Rails.logger.info { &amp;quot;Название: #{name}, Цена: #{price}&amp;quot; }
  end
rescue Selenium::WebDriver::Error::TimeoutError
  &amp;#x27;Элемент с классом product-name не найден&amp;#x27;
ensure
  driver.quit
end
&lt;/pre&gt;
  &lt;p id=&quot;zkDS&quot;&gt;И еще пара советов:&lt;/p&gt;
  &lt;ul id=&quot;vPAd&quot;&gt;
    &lt;li id=&quot;pDTO&quot;&gt;В браузер можно подгружать плагины:&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;EFh3&quot; data-lang=&quot;ruby&quot;&gt;options.add_extension(Rails.root.join(&amp;#x27;plugin.crx&amp;#x27;))&lt;/pre&gt;
  &lt;ul id=&quot;P3f0&quot;&gt;
    &lt;li id=&quot;hwy0&quot;&gt;Обязательно надо закрывать за собой браузер, чтобы избегать утечек памяти:&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;4mne&quot; data-lang=&quot;ruby&quot;&gt;driver.quit&lt;/pre&gt;
  &lt;ul id=&quot;Sf8G&quot;&gt;
    &lt;li id=&quot;VN1v&quot;&gt;Иногда требуется работа с cookie. В этом помогут следующие команды:&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;TD0D&quot; data-lang=&quot;ruby&quot;&gt;driver.manage.all_cookies # получить все куки
driver.manage.cookie_named # получить куку по названию
driver.manage.add_cookie # записать куку&lt;/pre&gt;
  &lt;h2 id=&quot;LZIm&quot;&gt;Альтернативы Selenium&lt;/h2&gt;
  &lt;p id=&quot;vK6l&quot;&gt;Самые популярные инструменты для работы с виртуальными браузерами это:&lt;/p&gt;
  &lt;ul id=&quot;CtDh&quot;&gt;
    &lt;li id=&quot;nJzP&quot;&gt;&lt;strong&gt;Selenium.&lt;/strong&gt; Старейший представитель. Поддержка множества браузеров и библиотек на различных языках. Большое и активное сообщество. Более сложное api, чем у остальных.&lt;/li&gt;
    &lt;li id=&quot;WdmT&quot;&gt;&lt;strong&gt;Puppeteer.&lt;/strong&gt; Работает только на Node.js. Поддержка Chrome и Firefox. Сообщество растет, но меньше по сравнению с Selenium. Простое и интуитивно понятное api.&lt;/li&gt;
    &lt;li id=&quot;Plyw&quot;&gt;&lt;strong&gt;Playwright.&lt;/strong&gt; Самый молодой представитель в этом списке. Поддержка множества браузеров и есть библиотеки на различных языках. Удобное и современное api. Встроенная поддержка мобильных устройств. Сообщество активно развивается.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;Y7Wk&quot;&gt;Вывод&lt;/h2&gt;
  &lt;p id=&quot;Ulav&quot;&gt;Selenium является одним из самых популярных инструментов для автоматизации браузеров и тестирования веб-приложений. Широкая поддержка различных браузеров и языков программирования упрощает процесс написания кода для реализации задач, будь то тестирование или парсинг.&lt;/p&gt;
  &lt;p id=&quot;Xfau&quot;&gt;Долговечность и стабильность подтверждают его надежность и эффективность.&lt;/p&gt;
  &lt;p id=&quot;4FDO&quot;&gt;С большой вероятностью проблема, которую вы пытаетесь решить, уже кем-то была решена, и в интернете можно найти гайд, который поможет.&lt;/p&gt;
  &lt;p id=&quot;hMpC&quot;&gt;Таким образом, Selenium остается одним из лучших инструментов для автоматизации браузеров. Используем его у себя в работе и вам советуем 🙂&lt;/p&gt;

</content></entry><entry><id>rnds:062-ai-assistant</id><link rel="alternate" type="text/html" href="https://blog.rnds.pro/062-ai-assistant?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rnds"></link><title>Простейший AI ассистент или Tools or not tools</title><published>2024-12-12T15:43:22.413Z</published><updated>2024-12-20T12:45:28.238Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/d6/87/d6875749-a2e1-42d6-a328-48a8e3196bc9.png"></media:thumbnail><category term="other" label="other"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/54/7f/547fc713-a025-45bd-bef5-914a420b1dca.jpeg&quot;&gt;Нужно бежать со всех ног, чтобы только                                                               оставаться на месте, а чтобы куда-то попасть,                                                                                        надо бежать как минимум вдвое быстрее!                                                                          Льюис Кэролл                                                                                                                Алиса в Cтране Чудес</summary><content type="html">
  &lt;blockquote id=&quot;5tYS&quot; data-align=&quot;right&quot;&gt;Нужно бежать со всех ног, чтобы только                                                               оставаться на месте, а чтобы куда-то попасть,                                                                                        надо бежать как минимум вдвое быстрее!                                                                          &lt;em&gt;Льюис Кэролл                                                                                                                Алиса в Cтране Чудес&lt;/em&gt;&lt;/blockquote&gt;
  &lt;figure id=&quot;bLku&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/54/7f/547fc713-a025-45bd-bef5-914a420b1dca.jpeg&quot; width=&quot;1043&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;RT8c&quot;&gt;&lt;strong&gt;Вступление&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;zgXP&quot;&gt;В данной статье мы продемонстрируем, как можно построить простейшего AI ассистента. Давайте сперва определимся с терминами. Обычно под AI ассистентом подразумевают способности Больших языковых моделей (далее по тексту LLM) не просто выдавать готовый текстовый ответ, но и совершать какую-то автономную работу по вызову сторонних функций, отправку запросов в API и на основании полученной информации из сторонних сервисов (но иногда нужно получить именно точный ответ в заданном формате), промпта, а также запроса пользователя выдавать итоговый ответ. Так, с терминами определились. Теперь вкратце о чем будет статья: в статье мы покажем, как 2мя способами LLM заставить взаимодействовать с внешним миром и с информацией, полученной не от пользователя, а из внешнего мира. В данной статье мы будем обогащать вывод LLM информацией из поиска, т.к. основной проблемой LLM является то, что в них информация заморожена на определенный момент времени, и с течением времени она устаревает и требует переобучения модели. Переобучение модели является очень дорогостоящим мероприятием, т.о. чтобы актуализировать информацию можно делать запросы в интернет, чтобы получать свежую информацию, а LLM будет нам, используя информацию из своего пространства знаний, а также дополняя информацией из поиска, выдавать достаточно свежий результат. На основе скриптов из этой статьи можно будет уже делать первые попытки для построения собственных мини-ассистентов.&lt;/p&gt;
  &lt;h3 id=&quot;2G79&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;NHX7&quot;&gt;&lt;strong&gt;Инструменты&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;hG82&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;qysk&quot;&gt;В данной статье будут использованы следующие технологии:&lt;/p&gt;
  &lt;ol id=&quot;Noiv&quot;&gt;
    &lt;li id=&quot;ePk8&quot;&gt;В качестве поискового движка будем использовать Tavily, т.к. у него простое API, а также они заявляют, что оптимизируют свой поиск как раз для использования с LLM (подробней можно почитать в документации к &lt;a href=&quot;https://docs.tavily.com/docs/welcome#tavily-search-api&quot; target=&quot;_blank&quot;&gt;tavily&lt;/a&gt;)&lt;/li&gt;
    &lt;li id=&quot;QJFG&quot;&gt;LLM YandexGPT 4 Pro 32k RC&lt;/li&gt;
    &lt;li id=&quot;HFir&quot;&gt;Python, Gradio&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;9rfi&quot;&gt;Нужно получить &lt;a href=&quot;https://yandex.cloud/ru/docs/foundation-models/api-ref/authentication#service-account_1&quot; target=&quot;_blank&quot;&gt;API ключи&lt;/a&gt; для YandexGPT API и &lt;a href=&quot;https://docs.tavily.com/docs/python-sdk/tavily-search/getting-started&quot; target=&quot;_blank&quot;&gt;ключ&lt;/a&gt; для Tavily&lt;/p&gt;
  &lt;p id=&quot;aqQF&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;vaqf&quot;&gt;&lt;strong&gt;Промптинг&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;Vkgj&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;1Gb9&quot;&gt;В первом способе будем использовать результаты вызова функции в промпте для получения окончательного ответа от LLM как самый примитивный способ.&lt;/p&gt;
  &lt;p id=&quot;blgm&quot;&gt;Ниже представлен код скрипта &lt;code&gt;yc-search-prompt.py&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;Syts&quot;&gt;&lt;/p&gt;
  &lt;pre id=&quot;4xH0&quot; data-lang=&quot;python&quot;&gt;#!/usr/bin/env python3
import httpx
import os
import gradio as gr
from tavily import TavilyClient

BASE_YC_GPT_URL = &amp;quot;https://llm.api.cloud.yandex.net/foundationModels/v1/completion&amp;quot;

def format_search_results(search_results):
  formatted_results = &amp;quot;\nRelevant search results:\n&amp;quot;
  for result in search_results[&amp;#x27;results&amp;#x27;]:
formatted_results += f&amp;quot;- {result[&amp;#x27;title&amp;#x27;]}: - URL: {result[&amp;#x27;url&amp;#x27;]}  \n {result[&amp;#x27;content&amp;#x27;][:200]}...\n&amp;quot;

def create_prompt_with_search(user_message, search_results):
  search_context = format_search_results(search_results)
  prompt = f&amp;quot;&amp;quot;&amp;quot;Here is some relevant context from a web search:
  {search_context}
  Using the above context, please answer the following question:
  {user_message}
  Please provide a comprehensive answer based on both the search results and your knowledge.
  And add at the end of final answer all titles and URL links at format Title - Url from above context.&amp;quot;&amp;quot;&amp;quot;
  return prompt
  
def make_search_request(text):
  tavily_client = TavilyClient(api_key=os.environ[&amp;quot;TAVILY_API_KEY&amp;quot;] )
  response = tavily_client.search(text, max_results=8)
  return response
  
def make_request_yc_gpt(text, history):
  with httpx.Client() as client:
  headers = {&amp;#x27;Authorization&amp;#x27;: &amp;quot;Api-Key &amp;quot; + os.environ[&amp;#x27;YC_API_KEY&amp;#x27;], 
  &amp;#x27;content-type&amp;#x27;:&amp;#x27;application/json&amp;#x27;}
  r = client.post(BASE_YC_GPT_URL, timeout=None, 
  json={&amp;quot;modelUri&amp;quot;: &amp;quot;gpt://&amp;quot;+os.environ[&amp;#x27;YC_FOLDER_ID&amp;#x27;] + &amp;quot;/yandexgpt-32k/rc&amp;quot;,
  &amp;quot;completionOptions&amp;quot;: {
  &amp;quot;stream&amp;quot;: False,
  &amp;quot;temperature&amp;quot;: &amp;quot;0.3&amp;quot;,
  &amp;quot;maxTokens&amp;quot;: &amp;quot;2000&amp;quot;
  },&amp;quot;messages&amp;quot;: [{&amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,&amp;quot;text&amp;quot;: text}]}, headers=headers)
  return r.json()[&amp;quot;result&amp;quot;][&amp;quot;alternatives&amp;quot;][0][&amp;quot;message&amp;quot;][&amp;quot;text&amp;quot;]

def chatbot_function(message, chat_history, model_choice):
  try:
    if model_choice == &amp;quot;YandexGPT+Tavily&amp;quot;:
      search_results = make_search_request(message)
      enhanced_prompt = create_prompt_with_search(message, search_results)
      print(enhanced_prompt)
      bot_message = f&amp;quot;You selected the {model_choice} model.\n&amp;quot; + make_request_yc_gpt(enhanced_prompt, chat_history)
      chat_history.append((message, bot_message))
    else:
      bot_message = f&amp;quot;You selected the {model_choice} model.\n&amp;quot; + make_request_yc_gpt(message, chat_history)
      chat_history.append((message, bot_message))
    return &amp;quot;&amp;quot;, chat_history
  except Exception as e:
    error_message = f&amp;quot;An error occurred: {str(e)}&amp;quot;
    chat_history.append((message, error_message))
    return &amp;quot;&amp;quot;, chat_history
with gr.Blocks() as demo:
  gr.Markdown(&amp;quot;AI prompting with internet search&amp;quot;)
  with gr.Row():
    with gr.Column(scale=4):
      chatbot = gr.Chatbot()
      msg = gr.Textbox(label=&amp;quot;Сообщение&amp;quot;)
      submit = gr.Button(&amp;quot;Отправить&amp;quot;)
      clear = gr.Button(&amp;quot;Очистить&amp;quot;)
    with gr.Column(scale=1):
      model = gr.Radio(
          [&amp;quot;YandexGPT+Tavily&amp;quot;, &amp;quot;YandexGPT&amp;quot;],
          label=&amp;quot;Выберите модель&amp;quot;,
          value=&amp;quot;YandexGPT+Tavily&amp;quot;
        )
  submit.click(chatbot_function, inputs=[msg, chatbot, model], outputs=[msg, chatbot])
  msg.submit(chatbot_function, inputs=[msg, chatbot, model], outputs=[msg, chatbot])
  clear.click(lambda: None, None, chatbot, queue=False)
demo.launch()
  &lt;/pre&gt;
  &lt;p id=&quot;OINZ&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;l9iW&quot;&gt;&lt;em&gt;Примечание:&lt;/em&gt; &lt;strong&gt;Помните: при запросах к YandexGPT и Tavily могут списываться денежные средства. Перед запуском скрипта читайте актуальные правила использования сервисов.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;KUff&quot;&gt;Запускать скрипт следующим образом &lt;/p&gt;
  &lt;pre id=&quot;Uxf4&quot;&gt;YC_FOLDER_ID=folder_id YC_API_KEY=YANDEX_API_KEY TAVILY_API_KEY=TAVILY_KEY  python3 yc-search-prompt.py&lt;/pre&gt;
  &lt;p id=&quot;qaiq&quot;&gt;Принцип работы скрипта: В UI Gradio на вход скрипт принимает текст от пользователя, в зависимости от того выбран ли вариант использования вместе с Tavily (YandexGPT+Tavily), тогда отправляется запрос в поиск Tavily, потом результат поиска отдается YandexGPT с промптом и просьбой сформировать окончательный ответ из собственных знаний, а также результатов поиска, а также в ответ добавить ссылки на источники из поиска.&lt;/p&gt;
  &lt;p id=&quot;S0g5&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;cbcu&quot;&gt;&lt;strong&gt;Function calling (Tools)&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;zJjy&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;QCIy&quot;&gt;Второй способ также будет использовать промпт для получения окончательного ответа, но для получения результатов поиска мы будем использовать функционал function calling. Наверное стоит остановиться подробней, для чего это нужно, т.к. кода стало почти в 2 раза больше, а результат такой же. Function calling (Tools) - это способность LLM вызывать сторонние приложения, это могут быть скрипты, обращения к различным API. В большинстве случаев это необходимо, когда для LLM нужно получить конкретный ответ (конечно, пример с использованием поиска не очень подходящий, но хотелось сделать примеры максимально похожими, больше здесь подходит, например, вызов функции, которая использует калькулятор), что-то посчитать, а т.к. LLM не предназначены для конкретных вычислений, то для этого используется функционал function calling. При этом если Tools будет много, то LLM может и сама принимать решение, когда и какой Tool ей вызывать (у anthropic есть прямо &lt;a href=&quot;https://docs.anthropic.com/en/docs/build-with-claude/tool-use#controlling-claudes-output&quot; target=&quot;_blank&quot;&gt;определение поведения&lt;/a&gt; LLM для выбора Tools. Важный момент: нужно делать хорошее описание для tools. Вот примеры хороших и плохих описаний tools от одного из &lt;a href=&quot;https://docs.anthropic.com/en/docs/build-with-claude/tool-use#best-practices-for-tool-definitions&quot; target=&quot;_blank&quot;&gt;лидеров индустрии&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;XHYo&quot;&gt;&lt;em&gt;Примечания:&lt;/em&gt;&lt;/p&gt;
  &lt;ol id=&quot;Cln8&quot;&gt;
    &lt;li id=&quot;5VLi&quot;&gt;&lt;em&gt;в YandexGPT API на момент написания статьи функционал Tools находился в режиме бета-тестирования, может быть непредвиденное поведение.&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;IjcR&quot;&gt;&lt;em&gt;На момент написания статьи в скриптах использовалась версия релиз кандидат YandexGPT RC 32k, подробней про жизненный цикл моделей &lt;a href=&quot;https://yandex.cloud/ru/docs/foundation-models/concepts/yandexgpt/models#model-lifecycle&quot; target=&quot;_blank&quot;&gt;читайте в документации&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;XqJj&quot;&gt;Ниже представлен код скрипта &lt;code&gt;yc-search-tools.py&lt;/code&gt;&lt;/p&gt;
  &lt;pre id=&quot;0IXS&quot; data-lang=&quot;python&quot;&gt;#!/usr/bin/env python3

import httpx
import os
import json
from tavily import TavilyClient
import gradio as gr

BASE_YC_GPT_URL = &amp;quot;https://llm.api.cloud.yandex.net/foundationModels/v1/completion&amp;quot;
search_tool = {
  &amp;quot;function&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;search_tavily&amp;quot;,
    &amp;quot;description&amp;quot;: &amp;quot;Search the web for current information&amp;quot;,
      &amp;quot;parameters&amp;quot;: {
        &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
        &amp;quot;properties&amp;quot;: {
          &amp;quot;query&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;The search query&amp;quot;
          }
        },
        &amp;quot;required&amp;quot;: [&amp;quot;query&amp;quot;]
      }
  }
}

def format_search_results(search_results):
  formatted_results = &amp;quot;\nRelevant search results:\n&amp;quot;
  for result in search_results[&amp;#x27;results&amp;#x27;]:
    formatted_results += f&amp;quot;- {result[&amp;#x27;title&amp;#x27;]}: - URL: {result[&amp;#x27;url&amp;#x27;]}  \n {result[&amp;#x27;content&amp;#x27;][:200]}...\n&amp;quot;
  return formatted_results

def create_prompt_with_search(user_message, search_results):
  search_context = format_search_results(search_results)
  prompt = f&amp;quot;&amp;quot;&amp;quot;Here is some relevant context from a web search:
{search_context}

Using the above context, please answer the following question:
{user_message}

Please provide a comprehensive answer based on both the search results and your knowledge.
And add at the end of final answer all titles and URL links at format Title - Url from above context.&amp;quot;&amp;quot;&amp;quot;
  return prompt

def make_request_yc_gpt(text, is_tool_call=True):
  with httpx.Client() as client:
    headers = {&amp;#x27;Authorization&amp;#x27;: &amp;quot;Api-Key &amp;quot; + os.environ[&amp;#x27;YC_API_KEY&amp;#x27;], 
    &amp;#x27;content-type&amp;#x27;:&amp;#x27;application/json&amp;#x27;}
    payload = {
      &amp;quot;modelUri&amp;quot;: f&amp;quot;gpt://{os.environ[&amp;#x27;YC_FOLDER_ID&amp;#x27;]}/yandexgpt-32k/rc&amp;quot;,
      &amp;quot;completionOptions&amp;quot;: {
        &amp;quot;stream&amp;quot;: False,
        &amp;quot;temperature&amp;quot;: 0.0,
        &amp;quot;maxTokens&amp;quot;: 8000
      },
      &amp;quot;messages&amp;quot;: text
    }
    
    if is_tool_call:
      payload[&amp;quot;tools&amp;quot;] = [search_tool]

    r = client.post(
    BASE_YC_GPT_URL,
    timeout=None,
    json=payload,
    headers=headers
    )
    
    response = r.json()
  return response

def handle_tool_calls(toolCalls):
  results = []
  for tool_call in toolCalls:
    if toolCalls[0][&amp;quot;functionCall&amp;quot;][&amp;quot;name&amp;quot;] == &amp;quot;search_tavily&amp;quot;:
      result = make_search_request(tool_call[&amp;quot;functionCall&amp;quot;][&amp;quot;arguments&amp;quot;][&amp;quot;query&amp;quot;])
  return result
def make_search_request(text):
  tavily_client = TavilyClient(api_key=os.environ[&amp;quot;TAVILY_API_KEY&amp;quot;] )
  response = tavily_client.search(text, max_results=8)
  return response

def process_conversation(user_input, history):
  conversation = [
    {
      &amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;,
      &amp;quot;text&amp;quot;: &amp;quot;You are a helpful bot that helps the user. You can use tools at your discretion to generate answers, but you don&amp;#x27;t always need to use them.&amp;quot;
    },
    {
      &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,
      &amp;quot;text&amp;quot;: user_input
    }
  ]
  initial_response = make_request_yc_gpt(conversation)
  
  if &amp;quot;toolCalls&amp;quot; in initial_response[&amp;#x27;result&amp;#x27;][&amp;#x27;alternatives&amp;#x27;][0][&amp;#x27;message&amp;#x27;][&amp;#x27;toolCallList&amp;#x27;]:
    tool_results = handle_tool_calls(
      initial_response[&amp;#x27;result&amp;#x27;][&amp;#x27;alternatives&amp;#x27;][0][&amp;#x27;message&amp;#x27;][&amp;#x27;toolCallList&amp;#x27;][&amp;quot;toolCalls&amp;quot;]
    )
    
    enhanced_prompt = create_prompt_with_search(user_input, tool_results)
    
    final_conversation = [
      {
        &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;,
        &amp;quot;text&amp;quot;: enhanced_prompt
      }
    ]
    
    final_response = make_request_yc_gpt(final_conversation, is_tool_call=False)
    return final_response[&amp;#x27;result&amp;#x27;][&amp;#x27;alternatives&amp;#x27;][0][&amp;#x27;message&amp;#x27;][&amp;#x27;text&amp;#x27;]
  else:
    return initial_response[&amp;#x27;result&amp;#x27;][&amp;#x27;alternatives&amp;#x27;][0][&amp;#x27;message&amp;#x27;][&amp;#x27;text&amp;#x27;]
    
def chatbot_function(message, chat_history, model_choice):
  try:
    response_list = process_conversation(message, chat_history)
    
    if isinstance(response_list, list):
      formatted_responses = [item[&amp;#x27;text&amp;#x27;] for item in response_list if isinstance(item, dict) and &amp;#x27;text&amp;#x27; in item] 
      bot_message = f&amp;quot;You selected the {model_choice} model.\n&amp;quot; + &amp;quot;\n&amp;quot;.join(formatted_responses)
    else:
      bot_message = f&amp;quot;You selected the {model_choice} model.\n&amp;quot; + str(response_list)
    chat_history.append((message, bot_message))
    return &amp;quot;&amp;quot;, chat_history 
  except Exception as e: 
    error_message = f&amp;quot;An error occurred: {str(e)}&amp;quot; 
    chat_history.append((message, error_message)) 
    return &amp;quot;&amp;quot;, chat_history
with gr.Blocks() as demo:
  gr.Markdown(&amp;quot;AI function calling tools internet search&amp;quot;) 
    with gr.Row(): 
      with gr.Column(scale=4): 
        chatbot = gr.Chatbot() 
        msg = gr.Textbox(label=&amp;quot;Сообщение&amp;quot;) 
        submit = gr.Button(&amp;quot;Отправить&amp;quot;) 
        clear = gr.Button(&amp;quot;Очистить&amp;quot;) 
      with gr.Column(scale=1):
        model = gr.Radio( 
          [&amp;quot;YandexGPT+Tavily&amp;quot;], 
          label=&amp;quot;Модель&amp;quot;, 
          value=&amp;quot;YandexGPT+Tavily&amp;quot; 
        ) 
  submit.click(chatbot_function, inputs=[msg, chatbot, model], outputs=[msg, chatbot]) 
  msg.submit(chatbot_function, inputs=[msg, chatbot, model], outputs=[msg, chatbot]) 
  clear.click(lambda: None, None, chatbot, queue=False) 
demo.launch()&lt;/pre&gt;
  &lt;p id=&quot;9BqZ&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Dhof&quot;&gt;&lt;em&gt;Примечание:&lt;/em&gt; &lt;strong&gt;Помните, при запросах к YandexGPT и Tavily могут списываться денежные средства. Перед запуском скрипта читайте актуальные правила использования сервисов.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;IIMp&quot;&gt;Запускать скрипт следующим образом&lt;/p&gt;
  &lt;p id=&quot;tb1h&quot;&gt;&lt;code&gt;YC_FOLDER_ID=folder_id YC_API_KEY=YANDEX_API_KEY TAVILY_API_KEY=TAVILY_KEY  python3 yc-search-tools.py&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;gfe5&quot;&gt;Вкратце принцип работы скрипта: В UI Gradio на вход скрипт принимает текст от пользователя, вызывает tool tavily_search, после этого результаты поиска отдается YandexGPt с промптом и просьбой сформировать окончательный ответ из собственных знаний, результатов поиска, а также в ответ добавить ссылки на источники из поиска.&lt;/p&gt;
  &lt;p id=&quot;kn2A&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;4dEr&quot;&gt;&lt;strong&gt;Итоги и дополнительные материалы&lt;/strong&gt;&lt;/h3&gt;
  &lt;p id=&quot;9Pwm&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Gk1i&quot;&gt;В итоге мы получили простенький аналог perplexity.ai, сделанный своими руками. Несколько скринов как это выглядит.&lt;/p&gt;
  &lt;p id=&quot;IVll&quot;&gt;Ответ YandexGPT, дополненный информацией из Tavily&lt;/p&gt;
  &lt;figure id=&quot;aVZP&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXcJbJAI8kAVt__Oh7QxUEfbEdLVUd1PM_8QhHozgTxkYysnJy-TwswWc5LHTALoKykLIxo5NcPFQysdTYPf2aTgNv8O2jRi-MoS5bcvVwiprp_u5U8KOrEeYgf_MWVeC58PM1Oo1w?key=UhEGguG-LoZ_EPfcxuJK_gbd&quot; width=&quot;865.7160120845922&quot; /&gt;
    &lt;figcaption&gt;YandexGPT дополненный информацией из Tavily&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;lkCN&quot;&gt;И ниже ответ дополняется ссылками (блок Titles and URLs) в поиске&lt;/p&gt;
  &lt;figure id=&quot;UZ4v&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXeGwTUa0egcd0d3Ul4ZnRlsdnRm4Oth5r9L2S8LUl7A-L-u0xdQlxQsNi9glUEygtrIvZlg_4LclUISlB7bppvD3f2h7j8c5lr-hbymRtHia2JjMgUIyWytv9pECVQ3SwmK5n71DA?key=UhEGguG-LoZ_EPfcxuJK_gbd&quot; width=&quot;869.2882882882883&quot; /&gt;
    &lt;figcaption&gt;YandexGPT дополненный информацией из Tavily&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;VL2I&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;E8HD&quot;&gt;Ответ YandexGPT без дополнения ответа результатами из поиска, как видно внизу без ссылок на результаты поиска&lt;/p&gt;
  &lt;figure id=&quot;rSTq&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXf1nEIXuj2_I2fENHgvryV7nbi4dMTKjjwdR7u3RnFDXz-NRaawv8-sSqoKSk0Y1SHgQnsjR5eRQn6fsXS5SUfbrF6pz0p5LqHUt5zR-SJzoQI9Zwq4pIrlq_l8bh-kypZOXUvl7Q?key=UhEGguG-LoZ_EPfcxuJK_gbd&quot; width=&quot;873.4893617021277&quot; /&gt;
    &lt;figcaption&gt;YandexGPT без дополненным ответом из поиска&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;AOEl&quot;&gt;Приводим аналогичные скрины с использованием функционала Tools&lt;/p&gt;
  &lt;figure id=&quot;viHD&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXdQxydh47aqy9Z0CUGYvsn57NT9l79qLo_Rdm2K94Xabl-AeI3JXCZSjsE_esTQ02RutkkCQU1dzAT9pdgGeG8haHNybUrLEylXEfHFf0I_HVthKogWFFTWl_d_H_FqYCHRkZwS?key=UhEGguG-LoZ_EPfcxuJK_gbd&quot; width=&quot;871.4434250764525&quot; /&gt;
    &lt;figcaption&gt;YandexGPT + Tavily + Function calling (Tools)&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure id=&quot;zxm7&quot; class=&quot;m_custom&quot; data-caption-align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXfS0QQxLYPTKE3p_QkRlrqBIsq2GHCmDhFnXQ8KwASXqkLOEJbVoiq4wztaGQ8CnfbcT0-Z2s8O1623EhOsa5VwSefNHimaJbikHIBJcFKIXplvQZcLf0nouWzggTRFklbyFmy3gA?key=UhEGguG-LoZ_EPfcxuJK_gbd&quot; width=&quot;873.1208459214503&quot; /&gt;
    &lt;figcaption&gt;YandexGPT + Tavily + Function calling (Tools)&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;z1MA&quot;&gt;Одним из самых частых препятствий в процессе промышленного внедрения LLM являются галлюцинации и проблемы получения конкретных ответов, и хочется сказать еще пару слов про библиотеки и фреймворки, которые могут быть полезны для решения этих проблем.&lt;/p&gt;
  &lt;ol id=&quot;y7pb&quot;&gt;
    &lt;li id=&quot;MJjm&quot;&gt;Не пренебрегайте промптами, если у вас нет других инструментов для контроля LLM.  Хороший материал на тему &lt;a href=&quot;https://www.promptingguide.ai/&quot; target=&quot;_blank&quot;&gt;prompt engineering&lt;/a&gt; &lt;/li&gt;
    &lt;li id=&quot;Fovi&quot;&gt;Кроме промптинга есть фреймворки для работы с промптами, например, &lt;a href=&quot;https://dspy.ai/&quot; target=&quot;_blank&quot;&gt;dspy&lt;/a&gt;, к сожалению, из коробки поддержки YandexGPT там нет, но можно пробовать использовать &lt;a href=&quot;https://github.com/all-mute/openai2yandex_api_adapter&quot; target=&quot;_blank&quot;&gt;адаптер для совместимости с OpenAI API&lt;/a&gt; + dspy (сами, честно говоря, еще не пробовали) и &lt;a href=&quot;https://yandex.cloud/ru/features/4631&quot; target=&quot;_blank&quot;&gt;проголосовать за фичу&lt;/a&gt; в Yandex Cloud&lt;/li&gt;
    &lt;li id=&quot;nzpF&quot;&gt;Помимо коммерческих реализаций есть также уже много open source моделей, в которых реализован функционал tools, например, у &lt;a href=&quot;https://ollama.com/search?c=tools&quot; target=&quot;_blank&quot;&gt;ollama&lt;/a&gt;. Кажется, скоро эта фича станет стандартной в LLM.&lt;/li&gt;
    &lt;li id=&quot;dqgO&quot;&gt;Кроме промптинга есть еще возможность заставить LLM четко следовать формату ответа, это т.н. structured output, вот &lt;a href=&quot;https://simmering.dev/blog/structured_output/&quot; target=&quot;_blank&quot;&gt;хорошая статья&lt;/a&gt; с библиотеками, большинство библиотек в этом списке тоже не поддерживают YandexGPT (а также почти все нестабильных версий 0.x.x), но чтобы этот мир стал еще лучше, можете проголосовать &lt;a href=&quot;https://yandex.cloud/ru/features/4627&quot; target=&quot;_blank&quot;&gt;за эту фичу&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;cVf9&quot;&gt;Фреймворки для построения агентов и мультиагентских систем&lt;/li&gt;
    &lt;ol id=&quot;silQ&quot;&gt;
      &lt;li id=&quot;nOMi&quot;&gt;&lt;a href=&quot;https://langchain-ai.github.io/langgraph/tutorials/introduction/&quot; target=&quot;_blank&quot;&gt;LangGraph&lt;/a&gt; кандидат, чтобы стать стандартом в индустрии (там готовится целая экосистема библиотек для работы с LLM LangChain, LangSmith, LangGraph), самая большая поддержка различных LLM. Очень нестабильно. Но в документации к LangGraph можно почерпнуть много хороших идей для построения агентов &lt;a href=&quot;https://langchain-ai.github.io/langgraph/tutorials/&quot; target=&quot;_blank&quot;&gt;Tutorials&lt;/a&gt; и &lt;a href=&quot;https://langchain-ai.github.io/langgraph/how-tos/&quot; target=&quot;_blank&quot;&gt;How-To&lt;/a&gt;, это прямо must read! Для построения MVP и быстрого прототипирования подходит отлично.&lt;/li&gt;
      &lt;li id=&quot;mdtH&quot;&gt;&lt;a href=&quot;https://docs.llamaindex.ai/en/stable/use_cases/agents/&quot; target=&quot;_blank&quot;&gt;Llamaindex&lt;/a&gt; Тоже довольно большая библиотека для работы с LLM. Подробней что-то  рассказать сложно, сами не пользовались.&lt;/li&gt;
      &lt;li id=&quot;C1v8&quot;&gt;&lt;a href=&quot;https://microsoft.github.io/autogen/0.2/&quot; target=&quot;_blank&quot;&gt;AutoGen&lt;/a&gt; библиотека для построения мультиагентских систем. Огромное кол-во примеров под разные use cases.&lt;/li&gt;
    &lt;/ol&gt;
    &lt;li id=&quot;TxIW&quot;&gt;Совсем недавно в Yandex cloud появился новый функционал &lt;a href=&quot;https://yandex.cloud/ru/docs/foundation-models/concepts/assistant/&quot; target=&quot;_blank&quot;&gt;AI assistant&lt;/a&gt;, который с использованием их ML SDK тоже позволяет строить LLM приложения и прячет некоторые вещи “под капот”, которые обычно используются в LLM приложениях: RAG, сохранение контекста.&lt;/li&gt;
    &lt;li id=&quot;N1Zl&quot;&gt;Также совсем недавно anthropic выпустил &lt;a href=&quot;https://modelcontextprotocol.io/introduction&quot; target=&quot;_blank&quot;&gt;Model Context Protocol&lt;/a&gt;, для более глубокой интеграции LLM, tools и источников данных. Посмотрим, сможет ли MCP стать стандартом в будущем.&lt;/li&gt;
    &lt;li id=&quot;MkmK&quot;&gt;Также аналогичный функционал, скорей всего, можно реализовать более простым способом, использовав лишь один инструмент &lt;a href=&quot;https://yandex.cloud/ru/docs/search-api/concepts/generative-response&quot; target=&quot;_blank&quot;&gt;Search API&lt;/a&gt; в Yandex Cloud (на момент написания статьи функционал был в Preview режиме)&lt;/li&gt;
  &lt;/ol&gt;

</content></entry><entry><id>rnds:061-mock-server</id><link rel="alternate" type="text/html" href="https://blog.rnds.pro/061-mock-server?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rnds"></link><title>Использование Mock-тестирования при интеграции с внешними системами</title><published>2024-12-03T05:37:28.565Z</published><updated>2024-12-09T09:52:17.065Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/d5/ee/d5eee923-e02f-48a0-b59c-4b2dbf7c287c.png"></media:thumbnail><category term="rails" label="rails"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/a9/75/a9755913-3a15-4b17-bdab-affd233b785a.jpeg&quot;&gt;У нас в RNDSOFT довольно частая ситуация когда при разработке приложения специалистам нужно взаимодействовать с внешними веб-сервисами или сторонними API. А когда такие взаимодействия несут потенциально опасные действия, хотелось бы как-то защититься и разрабатывать в некой безопасной среде.</summary><content type="html">
  &lt;figure id=&quot;A5Ey&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a9/75/a9755913-3a15-4b17-bdab-affd233b785a.jpeg&quot; width=&quot;1043&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;z6QH&quot;&gt;У нас в RNDSOFT довольно часто случается ситуация: при разработке приложения специалистам нужно взаимодействовать с внешними веб-сервисами или сторонними API. А когда такие взаимодействия несут потенциально опасные действия, хотелось бы как-то защититься и разрабатывать в некой безопасной среде.&lt;/p&gt;
  &lt;p id=&quot;sI2U&quot;&gt;Как бэкенд разработчик я могу воспользоваться mock-тестированием, это когда реальные объекты заменяются на контролируемые имитации.&lt;/p&gt;
  &lt;p id=&quot;to2E&quot;&gt;Например, в ruby, используя gem webmock, я могу перехватить реальный HTTP запрос от моего приложения и имитировать нужный мне ответ.&lt;/p&gt;
  &lt;p id=&quot;WgZ8&quot;&gt;Или с помощью gem vcr один раз записать HTTP запросы и ответы в файлы (кассеты) и в дальнейшем проигрывать эти кассеты вместо реальных запросов&lt;/p&gt;
  &lt;p id=&quot;cwnR&quot;&gt;В принципе, порог вхождения в такие инструменты не слишком большой, и их в обычных случаях хватает, чтобы не испытывать серьезных проблем в локальном окружении разработчика.&lt;/p&gt;
  &lt;p id=&quot;fJ0b&quot;&gt;А теперь представим, что вы QA специалист, и в некоторых случаях нужно изолировать тестируемый код от внешних зависимостей, ведь они могут замедлить или усложнить тестирование.&lt;/p&gt;
  &lt;p id=&quot;B7lH&quot;&gt;Скорей всего, вы не можете легко получить контроль над сторонними API, чтобы воспроизвести нужные сценарии. Конечно, некоторые сервисы предоставляют тестовое окружение, и тогда это может быть выходом из ситуации. А если нет такого тестового окружения или оно не предоставляет вам необходимую гибкость в реализации сценариев тестирования? Например, для проверки поведения системы нужно протестировать сценарии с ошибками или недоступностью компонентов.&lt;/p&gt;
  &lt;figure id=&quot;KlBI&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXexd7r1r5FWQlzfdviyCjyN4KbaQUR4CDmXlib9xlvmDTrcfsYNwY6D86uO2w-Gj47AOcAIXa-m_WzHU_SsRQzEnTSVEkkO3kFHtWsw4hoNCjwzKEJubYdx3Gy4OIHAqL9D3xcE_A?key=L6D4HjbNGB0GWpkp-n6iYlKH&quot; width=&quot;602&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;fI3j&quot;&gt;На помощь нам тоже приходит Mock-тестирование, но только реализовывается оно иначе, т.к. у тестировщика меньше возможностей вмешиваться в работу тестируемого кода.&lt;/p&gt;
  &lt;p id=&quot;ZP8U&quot;&gt;Необходимо поднять mock-сервер, который будет имитировать ответы внешнего API и настроить тестируемое приложение таким образом, чтобы оно обращалось к mock-серверу вместо реального. Обычно все URL для внешних API выносятся в переменные окружения у контейнера, тогда им легко управлять.&lt;/p&gt;
  &lt;p id=&quot;TAGH&quot;&gt;В некоторых случаях можно организовать обратный прокси, чтобы имитировать только часть запросов, а остальные перенаправлять на внешнее API.&lt;/p&gt;
  &lt;figure id=&quot;OX5Y&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXcgpmNE-ZUL7h2354NYM4yuhlOB8MX4wK1OUDNMYE1NLSWsDFaAUnpy29aLqkhUg7010RTG368Bt2NyrGYifRzPllYE71JK7yzRDNeagRl9zGLImpxlm6Lo5khyIwFe44R8sZ-63w?key=L6D4HjbNGB0GWpkp-n6iYlKH&quot; width=&quot;602&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;D8kT&quot;&gt;В интернете есть большое количество инструментов для mock-тестирования, принцип работы у них следующий:&lt;/p&gt;
  &lt;ol id=&quot;Qzj6&quot;&gt;
    &lt;li id=&quot;u30T&quot;&gt;Создаете ваше Mock API, разница в том, как это сделано:&lt;/li&gt;
    &lt;ol id=&quot;6kBw&quot;&gt;
      &lt;li id=&quot;oMwk&quot;&gt;Можно сгенерировать на основе OpenAPI Specification.&lt;/li&gt;
      &lt;li id=&quot;FjFQ&quot;&gt;Через графический интерфейс создать шаблоны запросов и ответов.&lt;/li&gt;
      &lt;li id=&quot;hXsU&quot;&gt;Создать шаблоны, используя интерфейс управления на основе REST API.&lt;/li&gt;
      &lt;li id=&quot;71bf&quot;&gt;Записать шаблоны на основе реальных запросов и отредактировать.&lt;/li&gt;
    &lt;/ol&gt;
    &lt;li id=&quot;MVG9&quot;&gt;Развернуть Mock-сервер локально или в некотором окружении, некоторые инструменты предлагают платные облака для развертывания mock-серверов.&lt;/li&gt;
    &lt;li id=&quot;osLC&quot;&gt;Далее нужно реализовать у вашего приложения возможность конфигурировать адреса внешних сервисов и перенаправить запросы на mock-сервер.&lt;/li&gt;
    &lt;li id=&quot;Iww1&quot;&gt;Используя возможности управления mock-сервером, проводить тестирования различных сценариев.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p id=&quot;G4wM&quot;&gt;Вот кратко про некоторые из инструментов для mock-тестирования:&lt;/p&gt;
  &lt;ul id=&quot;obsW&quot;&gt;
    &lt;li id=&quot;jSdp&quot;&gt;Postman - в основном используется для API тестирования, но у него есть возможность поднять mock-сервер, хорошо подходит для разовых mock сценариев тестирования.&lt;/li&gt;
    &lt;li id=&quot;NVd0&quot;&gt;SwaggerHub - моки создаются на основе OpenAPI Specification, и есть возможность по документации сгенерировать код готового mock-сервера под разные микрофреймворки.&lt;/li&gt;
    &lt;li id=&quot;Er9V&quot;&gt;CastleMock - поднимается через докер, есть панель управления, можно загрузить OpenAPI Specification.&lt;/li&gt;
    &lt;li id=&quot;VBAT&quot;&gt;Mockoon - работает локально, есть удобный GUI, можно загрузить OpenAPI Specification, есть возможность настроить обратный прокси и создавать моки на основе логов.&lt;/li&gt;
    &lt;li id=&quot;MaI0&quot;&gt;MockServer - можно поднять в докере, есть небольшая панель управления, основное управление осуществляется через REST API.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;MDai&quot;&gt;Это далеко не полный список и есть еще много других под разные стеки технологий!&lt;/p&gt;
  &lt;p id=&quot;I1zv&quot;&gt;Есть еще вариант написать свой mock-сервер, но думаю, это более долгий путь, который помимо времени разработчика может потребовать наличия соответствующих компетенций в разработке у тестировщика.&lt;/p&gt;
  &lt;p id=&quot;G5kq&quot;&gt;Мы в качестве решения взяли mockoon, из-за его особенностей он подходит под наши потребности. У него достаточно хорошая документация и мощный функционал.&lt;/p&gt;
  &lt;p id=&quot;iPjv&quot;&gt;Это бесплатное opensource кроссплатформенное приложение с графическим интерфейсом, есть версии для Linux, Windows и MacOS.&lt;/p&gt;
  &lt;figure id=&quot;4jAh&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXcf5jP1wXwM5BV5wd4A5vh1wSHceODS4iP7eYHJNBg6hGT5TR3SJZbUmxioMHsZcqWkXP1dj1EpFbMuJ2YjJV2jxmQpOQmw1jmY8hbQ8XwcR2GdyZ3ge-vvVtRSqXmn7D7f6XlqBw?key=L6D4HjbNGB0GWpkp-n6iYlKH&quot; width=&quot;597.7704918032787&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;nomQ&quot;&gt;Наша инфраструктура позволяет перенаправлять запросы с тестового контура на внутренние ip-адреса сотрудников, что упрощает развертывание mock-сервера. Поднял локально, сделал перенаправление с тестируемого приложения на свой IP и выбранный порт, и можно работать!&lt;/p&gt;
  &lt;figure id=&quot;hQum&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXdvQUmKCUMxV3R1H60YYZwXSpIZlXBb6CznmKgqU0mcFd4uCzKodcMoV1_JoujSnFsNtHdmES5t1DEOdX-fpAMaJ1KLM1Fdf9-6-1kzWh6mljVuv1I1Mf0T0wA-nq61mAh4j0EMzw?key=L6D4HjbNGB0GWpkp-n6iYlKH&quot; width=&quot;602&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;KgRK&quot;&gt;Либо можно подготовить коллекцию и поднять mockoon в докере на удаленной машине:&lt;/p&gt;
  &lt;pre id=&quot;aDv7&quot;&gt;$ docker run -d --mount type=bind,source=/data-file.json,target=/data,readonly 
             -p 3000:3000 mockoon/cli:latest 
             -d data -p 3000/&lt;/pre&gt;
  &lt;p id=&quot;SiDk&quot;&gt;Есть возможность сохранять коллекции шаблонов в json-файле, причем в виде, адаптированном под git, т.е. каждое ключ-значение на отдельной строке, а не сплошным месивом. Это позволяет держать коллекции для mock-сервера в репозитории проекта, обновлять его по мере разработки и использования разных частей внешнего API.&lt;/p&gt;
  &lt;figure id=&quot;yvVf&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXc-MIkjRVQpV5GsdKJHE7Bgm1orZaNyfeDfWSzKib6sIWIoEH9mG6mJhNAqaGlP2n18OyIyMsSvPmizrSF4ONJxafnd_hLrkk441alYTdSX5BANt5JCjKtHLjZjTc2XkrcjHp4BOw?key=L6D4HjbNGB0GWpkp-n6iYlKH&quot; width=&quot;602&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;dsEH&quot;&gt;Так же это позволяет на этапе разработки подготовить шаблоны запросов, которыми уже будет пользоваться тестировщик при прогоне разных сценариев.&lt;/p&gt;
  &lt;p id=&quot;qySt&quot;&gt;Для подготовки шаблонов есть возможность импортировать файл с OpenAPI Specification или настроить обратный прокси и создать шаблоны на основе полученных логов запросов, сервер работает таким образом, что сначала ищет варианты ответа среди созданных шаблонов, а если не нашел, то уже идет реальный запрос к стороннему API.&lt;/p&gt;
  &lt;p id=&quot;bFoh&quot;&gt;В принципе, можно в рамках одной коллекции настроить отображение разных сценариев, есть возможность создавать последовательности ответов, динамический шаблон ответа, настраивать условия в зависимости от параметров.&lt;/p&gt;
  &lt;p id=&quot;WEI5&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;B79Q&quot;&gt;Заключение&lt;/h2&gt;
  &lt;p id=&quot;6No4&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;KxjB&quot;&gt;Mock-тестирование позволяет разорвать зависимость от внешнего сервиса при разработке и тестировании. Что может быть необходимо в различных ситуациях, например, исключить потенциально опасные действия, протестировать негативные сценарии, разработать и протестировать фичу, когда внешний сервис еще не введен в эксплуатацию.&lt;/p&gt;
  &lt;p id=&quot;VasA&quot;&gt;Но надо сказать, что у данной технологии есть и недостатки. Шаблоны могут недостаточно полно имитировать поведение реальной системы. Поэтому вы можете не найти некоторые ошибки, которые возникают при реальном использовании системы.&lt;/p&gt;
  &lt;p id=&quot;8s44&quot;&gt;А неправильно настроенные шаблоны могут выдавать ложные результаты и, соответственно, приведут к ошибкам в процессе разработки.&lt;/p&gt;
  &lt;p id=&quot;ZsjI&quot;&gt;Несмотря на недостатки, это быстрый и дешевый метод протестировать различные сценарии поведения вашей системы, а на этапе разработки подготовить коллекции и поделиться с командой и тем самым снизить потенциальные временные затраты на поддержку зависимости от внешнего сервиса.&lt;/p&gt;

</content></entry><entry><id>rnds:060-the-halloween-problem-sql</id><link rel="alternate" type="text/html" href="https://blog.rnds.pro/060-the-halloween-problem-sql?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=rnds"></link><title>Сладость или гадость? Или жуткая и правдивая SQL-история</title><published>2024-10-30T12:49:06.170Z</published><updated>2024-10-31T11:48:26.895Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/52/a9/52a9c8f5-adf3-4b7c-87fb-a36c94188eb1.png"></media:thumbnail><category term="other" label="other"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/a7/8d/a78d78f1-3a47-44dc-96ad-e4fcff4372d3.jpeg&quot;&gt;Сегодня мы расскажем вам о проблеме Хэллоуина. Но для начала у меня для вас плохая новость: в этой истории нет ни призраков, ни зомби, ни даже вампиров.
Но сегодня Хэллоуин, так что мне показалось подходящим временем поднять эту тему после прочтения этой статьи. К тому же для тех, кто работает с реляционными базами данных, эта история не менее жуткая, потому как она о том, как простая SQL операция может преследовать вашу базу данных весьма неожиданным образом.</summary><content type="html">
  &lt;figure id=&quot;ybBG&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a7/8d/a78d78f1-3a47-44dc-96ad-e4fcff4372d3.jpeg&quot; width=&quot;1043&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;NbL4&quot;&gt;Сегодня мы расскажем вам о проблеме Хэллоуина. Но для начала у меня для вас плохая новость: в этой истории нет ни призраков, ни зомби, ни даже вампиров.&lt;br /&gt;Но сегодня Хэллоуин, так что мне показалось подходящим временем поднять эту тему после прочтения &lt;a href=&quot;https://www.cockroachlabs.com/blog/the-halloween-problem-sql/&quot; target=&quot;_blank&quot;&gt;этой статьи&lt;/a&gt;. К тому же для тех, кто работает с реляционными базами данных, эта история не менее жуткая, потому как она о том, как простая SQL операция может преследовать вашу базу данных весьма неожиданным образом.&lt;/p&gt;
  &lt;p id=&quot;MW8j&quot;&gt;Итак, давайте поговорим о «проблеме Хэллоуина» и выясним, действительно ли она так страшна.  Или всё-таки нет?&lt;/p&gt;
  &lt;p id=&quot;rswH&quot;&gt;Но для начала отмотаем на пару десятков лет назад.&lt;/p&gt;
  &lt;h2 id=&quot;72AG&quot;&gt;Почему она так называется?&lt;/h2&gt;
  &lt;p id=&quot;CQHI&quot;&gt;Это был обычный день в темной и жуткой комнате в 1975 году, когда группа инженеров обнаружила проблему с базой данных, выполняя довольно тривиальную задачу. Звали их &lt;a href=&quot;https://conservancy.umn.edu/server/api/core/bitstreams/a84ff7d0-95fb-4384-9e8c-18c055d3c644/content&quot; target=&quot;_blank&quot;&gt;Доном Чемберлином&lt;/a&gt;, Патрицией Сэлинджер и Мортоном Астраханом.&lt;/p&gt;
  &lt;figure id=&quot;ADld&quot; class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://lh7-rt.googleusercontent.com/docsz/AD_4nXeBVHAcSk5IV9PLjhYJl3ozv9Ss4H_3Bd6KBoqHW7Mw6ZeMJZwMsSzptFu59CH4R8oqoOUVeONTHl2qBT1dB2GG8bpFvZhN88MKVCg1JoKp1dVwwEgFY52rUqtkDMpKwOn2YSs5K_tg8l9EzZmPKFltfdps?key=qlMFLqoEyrSBtqA0F7ytBQ&quot; width=&quot;396&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;8HYq&quot;&gt;Они создали запрос в базе данных, предназначенный для увеличения зарплаты каждого сотрудника, зарабатывающего менее 25 000 долларов, на 10 процентов. &lt;/p&gt;
  &lt;p id=&quot;RBsP&quot;&gt;На первый взгляд, изменения в базе данных работали и не выдавали никаких ошибок. Но всё оказалось не так просто. Запрос не просто обновил зарплату на 10 процентов - он продолжал циклически просматривать базу данных, постоянно увеличивая каждую зарплату на 10 процентов, пока все не стали зарабатывать   25 000 долларов в год.&lt;/p&gt;
  &lt;p id=&quot;sOII&quot;&gt;А произошел данный инцидент в пятницу 31 октября. Поэтому было решено  увековечить его проблемой Хэллоуина.&lt;/p&gt;
  &lt;h2 id=&quot;3jSW&quot;&gt;Что такое «проблема Хэллоуина»?&lt;/h2&gt;
  &lt;p id=&quot;u0r6&quot;&gt;Так что же пошло не так? Давайте разбираться. Для этого предлагаю использовать тот же пример, что рассматривали Сэлинджер и Астрахан, когда обнаружили неладное.&lt;/p&gt;
  &lt;p id=&quot;s9Tx&quot;&gt;Представьте, что у нас есть таблица сотрудников и зарплат, в которой первые несколько строк выглядят примерно так:&lt;/p&gt;
  &lt;figure id=&quot;YtQL&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/12/89/12891527-53bf-4782-980e-f03da772288f.png&quot; width=&quot;464&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;IvhI&quot;&gt;Нам нужно обновить таблицу так, чтобы предоставить 10%-ную надбавку тем, кто зарабатывает менее 25 000 долларов в год. Да это же элементарно, подумаете вы и, скорее всего, набросаете вот такой sql запрос:&lt;/p&gt;
  &lt;pre id=&quot;cqtU&quot;&gt;UPDATE employee SET salary = salary * 1.1 WHERE salary &amp;lt; 25000;&lt;/pre&gt;
  &lt;p id=&quot;m5qG&quot;&gt;Мы ожидаем, что результатом этого запроса будет обновленная таблица, в которой зарплата Владимира не изменится, но зарплата Сергея обновится до 19360 долларов, а зарплата Александра обновится до 13530 долларов и т.д.&lt;/p&gt;
  &lt;p id=&quot;NuKP&quot;&gt;Однако, если наш запрос обрабатывается с использованием некластеризованного индекса, зарплаты могут обновляться постоянно, пока не достигнут 25000 долларов.&lt;/p&gt;
  &lt;p id=&quot;aWoT&quot;&gt;Но как такое возможно? Представьте, что это наш некластеризованный индекс, отсортированный по возрастанию по значениям в столбце «Зарплата»:&lt;/p&gt;
  &lt;figure id=&quot;F6Px&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/fc/29/fc292070-bdec-42a3-bb86-4407fabbf8e8.png&quot; width=&quot;464&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;3epe&quot;&gt;Если наш запрос будет выполняться итеративно с использованием этого индекса, то сначала будет просмотрена строка для Александра. Поскольку это значение ниже 25000, оно будет обновлено - согласно нашему запросу умножено на 1,1, так что новое значение составит 13530. Обновление этой строки также обновит индекс:&lt;/p&gt;
  &lt;figure id=&quot;p35J&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/c1/8e/c18e0393-7a6a-4e20-8014-63e913e96f23.png&quot; width=&quot;464&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;uHva&quot;&gt;Поскольку запрос выполняется итеративно, следующим шагом будет обновление строки Сергея, но мы уже видим здесь проблему: наше обновление строки Александра также изменило ее положение в индексе. И поскольку его значение все еще меньше 25000, обновление будет применено к нему снова, когда сканирование индекса достигнет этой точки в индексе.&lt;/p&gt;
  &lt;p id=&quot;t2ER&quot;&gt;Строки с зарплатами Сергея и Александра будут постоянно обгонять друг друга в индексе, оставаясь на шаг впереди сканирования индекса, пока оба значения не перейдут отметку 25000.&lt;/p&gt;
  &lt;h3 id=&quot;AcNF&quot;&gt;Подведем итог:&lt;/h3&gt;
  &lt;p id=&quot;aCTu&quot;&gt;Проблема Хэллоуина описывает явление, при котором запросы &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; и &lt;code&gt;MERGE&lt;/code&gt; в &lt;code&gt;SQL&lt;/code&gt; при определенных обстоятельствах могут привести к тому, что строки обновляются несколько раз во время операции, хотя должны обновляться только один раз. Или, возможно, даже обновляться в бесконечном цикле. Вот это поистине страшно!&lt;/p&gt;
  &lt;h2 id=&quot;HBlL&quot;&gt;Как победить проблему Хэллоуина?&lt;/h2&gt;
  &lt;p id=&quot;ulDq&quot;&gt;Оговоримся сразу, что в PostgreSQL этой проблемы нет. Проблема Хэллоуина - это ошибка в проектировании базы данных, и любая база данных с такой проблемой не надежна. Однако, некоторые СУБД по-прежнему имеют потенциал для возникновения подобных проблем.&lt;/p&gt;
  &lt;p id=&quot;Ll5J&quot;&gt;Так как мы в примере говорили о том, как проблема Хэллоуина применяется к &lt;code&gt;UPDATE&lt;/code&gt; запросам, то и решение будет описано конкретно для этого случая.&lt;/p&gt;
  &lt;p id=&quot;kR7G&quot;&gt;Одним из простых решений проблемы является физическое разделение курсоров чтения и записи в запросе с &lt;code&gt;UPDATE&lt;/code&gt; с помощью &lt;a href=&quot;https://mssqlforever.blogspot.com/2024/02/blog-post_29.html&quot; target=&quot;_blank&quot;&gt;оператора блокировки&lt;/a&gt;. Например, это может быть как активная буферизация, так и сортировка. Вставка оператора блокировки в середину плана &lt;code&gt;UPDATE&lt;/code&gt; гарантирует, что курсор чтения отработает полностью и получит все нужные строки прежде, чем курсор записи начнет изменять соответствующие строки. К сожалению, вставка оператора блокировки в такой план запроса потребует получения всех строк из курсора чтения, а это обычно обходится довольно дорого. К счастью, во многих случаях SQL Server может определить, что курсор записи не влияет на курсор чтения и не станет добавлять оператор блокировки.&lt;/p&gt;
  &lt;h2 id=&quot;86qf&quot;&gt;Почему проблема Хэллоуина – это головная боль, даже если она не особенно страшная?&lt;/h2&gt;
  &lt;p id=&quot;kevX&quot;&gt;По сути, поскольку вы не можете просто напрямую изменить несколько полей, чтобы не рисковать изменением данных, которые вы не собирались менять, вам придется использовать другие методы. Проблема в том, что эти методы, как правило, сложнее, чем очевидный вариант. Они могут быть либо медленнее, либо труднее в реализации.&lt;/p&gt;
  &lt;p id=&quot;IWNR&quot;&gt;Если вы хотите вникнуть в суть этой концепции, я рекомендую серию из &lt;a href=&quot;https://sqlperformance.com/2013/02/t-sql-queries/halloween-problem-part-1&quot; target=&quot;_blank&quot;&gt;четырех прекрасных статей Уайта&lt;/a&gt;. В цикле статей Пол Уайт объяснил многочисленные нюансы этой проблемы, а также трудности, которые она создает с точки зрения производительности при выполнении запросов к базе данных SQL без случайного изменения данных.&lt;/p&gt;

</content></entry></feed>