Inneal Togail Dàta no na tha cumanta eadar Data Warehouse agus Smoothie

Inneal Togail Dàta no na tha cumanta eadar Data Warehouse agus Smoothie
Dè na prionnsapalan a tha air leth freagarrach airson Stòr-dàta Dàta a thogail?

Fòcas air luach gnìomhachais agus anailis às aonais còd boilerplate. A’ riaghladh DWH mar bhun-chòd: dreachdadh, lèirmheas, deuchainn fèin-ghluasadach agus CI. Modular, leudachail, stòr fosgailte agus coimhearsnachd. Sgrìobhainnean furasta a chleachdadh agus fradharc eisimeileachd (Loidhne Dàta).

Barrachd mu dheidhinn seo uile agus mu àite DBT ann an eag-shiostam Big Data & Analytics - fàilte gu cat.

Halo a h-uile duine

Tha Artemy Kozyr ann an conaltradh. Airson còrr air 5 bliadhna tha mi air a bhith ag obair le taighean-bathair dàta, a’ togail ETL/ELT, a bharrachd air mion-sgrùdadh dàta agus fradharc. Tha mi ag obair ann an-dràsta Wheely, Bidh mi a’ teagasg cùrsa aig OTUS Innleadair Dàta, agus an-diugh tha mi airson artaigil a sgrìobh mi a cho-roinn riut an dùil an toiseach clàradh ùr airson a’ chùrsa.

Ath-sgrùdadh goirid

Tha frèam DBT uile mu dheidhinn an acronaim T anns an ELT (Earrann - Transform - Load).

Le teachd a-steach stòran-dàta anailis toraidh agus so-ruigsinneach mar BigQuery, Redshift, Snowflake, cha robh feum sam bith air cruth-atharrachaidhean a dhèanamh taobh a-muigh an Stòr Dàta. 

Cha bhith DBT a’ luchdachadh sìos dàta bho stòran, ach tha e a’ toirt seachad deagh chothroman airson obrachadh le dàta a chaidh a luchdachadh a-steach don Stòradh (ann an Stòradh a-staigh no a-muigh).

Inneal Togail Dàta no na tha cumanta eadar Data Warehouse agus Smoothie
Is e prìomh adhbhar DBT an còd a ghabhail, a chuir ri chèile ann an SQL, na h-òrdughan a chuir an gnìomh san t-sreath cheart san Stòr.

Structar pròiseact DBT

Tha am pròiseact air a dhèanamh suas de chlàran agus faidhlichean de dìreach 2 sheòrsa:

  • Modail (.sql) - aonad cruth-atharrachaidh air a chuir an cèill le ceist SELECT
  • Faidhle rèiteachaidh (.yml) - paramadairean, suidheachaidhean, deuchainnean, sgrìobhainnean

Aig ìre bhunaiteach, tha structar na h-obrach mar a leanas:

  • Bidh an neach-cleachdaidh ag ullachadh còd modail ann an IDE iomchaidh sam bith
  • A’ cleachdadh an CLI, thèid modalan a chuir air bhog, bidh DBT a’ cur ri chèile an còd modail ann an SQL
  • Tha an còd SQL cruinnichte air a chur gu bàs anns an Stòradh ann an sreath sònraichte (graf)

Seo cò ris a bhiodh ruith bhon CLI coltach:

Inneal Togail Dàta no na tha cumanta eadar Data Warehouse agus Smoothie

Tha a h-uile dad SELECT

Tha seo na fheart marbhtach den fhrèam Inneal Togail Dàta. Ann am faclan eile, tha DBT a’ toirt geàrr-chunntas air a’ chòd gu lèir co-cheangailte ri bhith a’ cur na ceistean agad a-steach don Stòr (caochlaidhean bho na h-òrdughan CREATE, INSERT, UPDATE, DELETE ALTER, GRANT, ...).

Tha modail sam bith a’ toirt a-steach sgrìobhadh aon cheist SELECT a mhìnicheas an t-seata dàta a thig às.

Anns a 'chùis seo, faodaidh an loidsig cruth-atharrachaidh a bhith ioma-ìre agus a' daingneachadh dàta bho ghrunn mhodail eile. Eisimpleir de mhodail a thogas taisbeanadh òrdugh (f_orders):

{% set payment_methods = ['credit_card', 'coupon', 'bank_transfer', 'gift_card'] %}
 
with orders as (
 
   select * from {{ ref('stg_orders') }}
 
),
 
order_payments as (
 
   select * from {{ ref('order_payments') }}
 
),
 
final as (
 
   select
       orders.order_id,
       orders.customer_id,
       orders.order_date,
       orders.status,
       {% for payment_method in payment_methods -%}
       order_payments.{{payment_method}}_amount,
       {% endfor -%}
       order_payments.total_amount as amount
   from orders
       left join order_payments using (order_id)
 
)
 
select * from final

Dè na rudan inntinneach a chì sinn an seo?

An toiseach: Chleachdadh CTE (Seallaidhean Clàr Coitcheann) - gus còd a chuir air dòigh agus a thuigsinn anns a bheil tòrr atharrachaidhean agus loidsig gnìomhachais

Dàrna: Tha còd modail na mheasgachadh de SQL agus cànan Jinja (cànan templating).

Tha an eisimpleir a 'cleachdadh lùb airson gus an t-suim a ghineadh airson gach dòigh pàighidh a tha air a shònrachadh san abairt seata. Tha an gnìomh cuideachd air a chleachdadh Prìomhachd - an comas iomradh a thoirt air modalan eile taobh a-staigh a’ chòd:

  • Aig àm cur ri chèile Prìomhachd thèid a thionndadh gu puing targaid gu bòrd no sealladh ann an Stòradh
  • Prìomhachd a’ leigeil leat graf eisimeileachd modail a thogail

Bha e Jinja a’ cur cothroman cha mhòr gun chrìoch ri DBT. Is iad an fheadhainn as cumanta:

  • Ma tha / eile aithrisean - aithrisean meur
  • Airson lùban
  • Caochlaidhean
  • Macro - cruthachadh macros

Stuthachadh: Clàr, Sealladh, Meudachadh

Tha ro-innleachd stuthan mar dhòigh-obrach a rèir an tèid an seata de dhàta modail a thig às a stòradh anns an Stòradh.

Ann an teirmean bunaiteach tha e:

  • Clàr - clàr corporra anns an Stòradh
  • Sealladh - sealladh, clàr brìgheil ann an Stòradh

Tha ro-innleachdan toraidh nas iom-fhillte ann cuideachd:

  • Meudachadh - luchdachadh mean air mhean (de chlàran fìrinn mòra); thèid loidhnichean ùra a chur ris, thèid loidhnichean atharraichte ùrachadh, thèid loidhnichean a chaidh a dhubhadh às a ghlanadh 
  • Ephemeral - chan eil am modail a 'tighinn gu buil gu dìreach, ach a' gabhail pàirt mar CTE ann am modalan eile
  • Ro-innleachdan sam bith eile as urrainn dhut fhèin a chur ris

A bharrachd air ro-innleachdan toraidh, tha cothroman ann airson optimization airson Stòrasan sònraichte, mar eisimpleir:

  • Snowflake: Clàran gluasadach, giùlan cothlamadh, cruinneachadh bùird, tabhartasan copaidh, seallaidhean tèarainte
  • Sgaoileadh: Distkey, Sortkey (interleaved, compound), Seallaidhean ceangail anmoch
  • Ceist Mhòir: sgaradh bùird & cruinneachadh, giùlan cothlamadh, crioptachadh KMS, bileagan & tagaichean
  • Spark: Fòrmat faidhle (parquet, csv, json, orc, delta), partition_by, clustered_by, bucaidean, incremental_strategy

Tha na stòran a leanas a’ faighinn taic an-dràsta:

  • postgres
  • Sgaoileadh
  • Ceist Mhòir
  • Snowflake
  • Presto (ann am pàirt)
  • Spark (gu ìre)
  • Microsoft SQL Server (adapter coimhearsnachd)

Nach leasaich sinn ar modail:

  • Dèanamaid an lìonadh aige mean air mhean (Àrd-ìre)
  • Nach cuir sinn iuchraichean sgaraidh is seòrsachaidh airson Redshift

-- Конфигурация модели: 
-- Инкрементальное наполнение, уникальный ключ для обновления записей (unique_key)
-- Ключ сегментации (dist), ключ сортировки (sort)
{{
  config(
       materialized='incremental',
       unique_key='order_id',
       dist="customer_id",
       sort="order_date"
   )
}}
 
{% set payment_methods = ['credit_card', 'coupon', 'bank_transfer', 'gift_card'] %}
 
with orders as (
 
   select * from {{ ref('stg_orders') }}
   where 1=1
   {% if is_incremental() -%}
       -- Этот фильтр будет применен только для инкрементального запуска
       and order_date >= (select max(order_date) from {{ this }})
   {%- endif %} 
 
),
 
order_payments as (
 
   select * from {{ ref('order_payments') }}
 
),
 
final as (
 
   select
       orders.order_id,
       orders.customer_id,
       orders.order_date,
       orders.status,
       {% for payment_method in payment_methods -%}
       order_payments.{{payment_method}}_amount,
       {% endfor -%}
       order_payments.total_amount as amount
   from orders
       left join order_payments using (order_id)
 
)
 
select * from final

Graf eisimeileachd modail

Tha e cuideachd na chraobh eisimeileachd. Tha e cuideachd air ainmeachadh mar DAG (Directed Acyclic Graph).

Bidh DBT a’ togail graf stèidhichte air rèiteachadh a h-uile modal pròiseict, no an àite, ceanglaichean ref() taobh a-staigh mhodalan gu modalan eile. Le bhith a’ faighinn graf leigidh sin leat na rudan a leanas a dhèanamh:

  • Modalan ruith san t-sreath cheart
  • Co-shìnteadh ri cruthachadh aghaidh stòr
  • A 'ruith fo-sgrìobhadh neo-riaghailteach 

Eisimpleir de dhealbh grafa:

Inneal Togail Dàta no na tha cumanta eadar Data Warehouse agus Smoothie
Tha gach nód den ghraf na mhodail; tha oirean a’ ghraf air an sònrachadh leis an abairt re.

Càileachd an dàta agus sgrìobhainnean

A bharrachd air na modalan fhèin a chruthachadh, leigidh DBT leat grunn bharailean a dhearbhadh mun t-seata dàta a thig às, leithid:

  • Chan e Null
  • Unique
  • Ionracas iomraidh - ionracas iomraidh (mar eisimpleir, tha customer_id anns a’ chlàr òrdughan a’ freagairt ri id ann an clàr an luchd-ceannach)
  • A’ freagairt ris an liosta de luachan iomchaidh

Tha e comasach na deuchainnean agad fhèin (deuchainnean dàta gnàthaichte) a chuir ris, leithid, mar eisimpleir, % claonadh teachd-a-steach le comharran bho latha, seachdain, mìos air ais. Faodaidh beachd sam bith a thèid a dhealbhadh mar cheist SQL a bhith na dheuchainn.

San dòigh seo, faodaidh tu glacaidhean gun iarraidh agus mearachdan ann an dàta a ghlacadh ann an uinneagan an taigh-bathair.

A thaobh sgrìobhainnean, tha DBT a’ toirt seachad dòighean airson meata-dàta agus beachdan a chur ris, a dhreach agus a sgaoileadh aig a’ mhodail agus eadhon ìrean buadhan. 

Seo cò ris a bhios deuchainnean agus sgrìobhainnean a’ coimhead coltach aig ìre an fhaidhle rèiteachaidh:

 - name: fct_orders
   description: This table has basic information about orders, as well as some derived facts based on payments
   columns:
     - name: order_id
       tests:
         - unique # проверка на уникальность значений
         - not_null # проверка на наличие null
       description: This is a unique identifier for an order
     - name: customer_id
       description: Foreign key to the customers table
       tests:
         - not_null
         - relationships: # проверка ссылочной целостности
             to: ref('dim_customers')
             field: customer_id
     - name: order_date
       description: Date (UTC) that the order was placed
     - name: status
       description: '{{ doc("orders_status") }}'
       tests:
         - accepted_values: # проверка на допустимые значения
             values: ['placed', 'shipped', 'completed', 'return_pending', 'returned']

Agus seo cò ris a tha an sgrìobhainn seo coltach air an làrach-lìn a chaidh a chruthachadh:

Inneal Togail Dàta no na tha cumanta eadar Data Warehouse agus Smoothie

Macros agus modalan

Chan e adhbhar DBT a bhith na sheata de sgriobtaichean SQL cho mòr, ach gus dòigh chumhachdach agus beairteach a thoirt do luchd-cleachdaidh airson na h-atharrachaidhean aca fhèin a thogail agus na modalan sin a sgaoileadh.

Is e seataichean de chruthan is abairtean a th’ ann am macros a dh’ fhaodar a ghairm mar ghnìomhan taobh a-staigh mhodalan. Leigidh Macros leat SQL ath-chleachdadh eadar modailean agus pròiseactan a rèir prionnsapal innleadaireachd DRY (Na dèan ath-aithris ort fhèin).

Eisimpleir macro:

{% macro rename_category(column_name) %}
case
 when {{ column_name }} ilike  '%osx%' then 'osx'
 when {{ column_name }} ilike  '%android%' then 'android'
 when {{ column_name }} ilike  '%ios%' then 'ios'
 else 'other'
end as renamed_product
{% endmacro %}

Agus a chleachdadh:

{% set column_name = 'product' %}
select
 product,
 {{ rename_category(column_name) }} -- вызов макроса
from my_table

Bidh DBT a’ tighinn le manaidsear pacaid a leigeas le luchd-cleachdaidh modalan agus macros fa leth fhoillseachadh agus ath-chleachdadh.

Tha seo a’ ciallachadh a bhith comasach air leabharlannan a luchdachadh agus a chleachdadh leithid:

  • dbt_utils: ag obair le Ceann-latha/Ùine, Iuchraichean Ionadail, deuchainnean sgeama, Pivot/Unpivot agus feadhainn eile
  • Teamplaidean taisbeanaidh deiseil airson seirbheisean leithid Sruth-sneachda и Stiall 
  • Leabharlannan airson Stòran Dàta sònraichte, m.e. Sgaoileadh 
  • Logadh - Modal airson gnìomhachd DBT a logadh

Gheibhear liosta iomlan de phasganan aig ionad dbt.

Fiù 's barrachd fheartan

An seo bheir mi cunntas air beagan fheartan inntinneach eile agus buileachadh a bhios an sgioba agus mi a’ cleachdadh gus Stòr-dàta a thogail ann Wheely.

A’ sgaradh àrainneachdan runtime DEV - TEST - PROD

Eadhon taobh a-staigh an aon bhuidheann DWH (taobh a-staigh sgeamaichean eadar-dhealaichte). Mar eisimpleir, a 'cleachdadh an abairt a leanas:

with source as (
 
   select * from {{ source('salesforce', 'users') }}
   where 1=1
   {%- if target.name in ['dev', 'test', 'ci'] -%}           
       where timestamp >= dateadd(day, -3, current_date)   
   {%- endif -%}
 
)

Tha an còd seo ag ràdh gu litireil: airson àrainneachdan dev, deuchainn, ci gabh dàta a-mhàin airson na 3 latha mu dheireadh agus gun a bhith nas fhaide. Is e sin, bidh ruith anns na h-àrainneachdan sin fada nas luaithe agus bidh feum air nas lugha de ghoireasan. Nuair a bhios tu a 'ruith air an àrainneachd prod cha tèid an suidheachadh sìoltachain a leigeil seachad.

Stuthachadh le còdachadh colbh eile

Tha Redshift na DBMS colbh a leigeas leat algorithms teannachaidh dàta a shuidheachadh airson gach colbh fa leth. Le bhith a’ taghadh na h-algorithms as fheàrr faodaidh sin àite diosc a lughdachadh 20-50%.

Macro redshift.compress_table cuiridh e an gnìomh an àithne ANALYZE COMPRESSION, cruthaich clàr ùr leis na h-algorithms còdaidh colbh a thathar a’ moladh, iuchraichean sgaraidh ainmichte (dist_key) agus iuchraichean rèiteach (sort_key), gluais an dàta thuige, agus, ma tha sin riatanach, cuir às don t-seann leth-bhreac.

Ainm-sgrìobhte macro:

{{ compress_table(schema, table,
                 drop_backup=False,
                 comprows=none|Integer,
                 sort_style=none|compound|interleaved,
                 sort_keys=none|List<String>,
                 dist_style=none|all|even,
                 dist_key=none|String) }}

Tha modail logaidh a 'ruith

Faodaidh tu dubhan a cheangal ri gach coileanadh den mhodail, a thèid a chuir gu bàs mus tèid a chuir air bhog no dìreach às deidh cruthachadh a ’mhodail a chrìochnachadh:

   pre-hook: "{{ logging.log_model_start_event() }}"
   post-hook: "{{ logging.log_model_end_event() }}"

Leigidh am modal logaidh dhut na meata-dàta riatanach uile a chlàradh ann an clàr air leth, a dh’ fhaodar a chleachdadh às deidh sin gus botail a sgrùdadh agus a sgrùdadh.

Seo cò ris a tha an deas-bhòrd coltach stèidhichte air dàta logaidh ann an Looker:

Inneal Togail Dàta no na tha cumanta eadar Data Warehouse agus Smoothie

Automation de chumail suas stòraidh

Ma chleachdas tu cuid de leudachaidhean air comas-gnìomh an Stòr-tasgaidh a chaidh a chleachdadh, leithid UDF (Gnìomhan Mìnichte a’ Chleachdaiche), tha e glè ghoireasach a dhèanamh ann an DBT a bhith a’ tionndadh nan gnìomhan sin, smachd ruigsinneachd, agus sgaoileadh fèin-ghluasadach de sgaoilidhean ùra.

Bidh sinn a’ cleachdadh UDF ann am Python gus hashes obrachadh a-mach, raointean post-d, agus còdachadh bitmask.

Eisimpleir de macro a chruthaicheas UDF air àrainneachd cur gu bàs sam bith (dev, test, prod):

{% macro create_udf() -%}
 
 {% set sql %}
       CREATE OR REPLACE FUNCTION {{ target.schema }}.f_sha256(mes "varchar")
           RETURNS varchar
           LANGUAGE plpythonu
           STABLE
       AS $$  
           import hashlib
           return hashlib.sha256(mes).hexdigest()
       $$
       ;
 {% endset %}
  
 {% set table = run_query(sql) %}
 
{%- endmacro %}

Aig Wheely bidh sinn a’ cleachdadh Amazon Redshift, a tha stèidhichte air PostgreSQL. Airson Redshift, tha e cudromach staitistig a chruinneachadh gu cunbhalach air bùird agus àite diosc a shaoradh - na h-òrdughan ANALYZE agus VACUUM, fa leth.

Gus seo a dhèanamh, thèid na h-òrdughan bhon macro redshift_maintenance a chuir gu bàs gach oidhche:

{% macro redshift_maintenance() %}
 
   {% set vacuumable_tables=run_query(vacuumable_tables_sql) %}
 
   {% for row in vacuumable_tables %}
       {% set message_prefix=loop.index ~ " of " ~ loop.length %}
 
       {%- set relation_to_vacuum = adapter.get_relation(
                                               database=row['table_database'],
                                               schema=row['table_schema'],
                                               identifier=row['table_name']
                                   ) -%}
       {% do run_query("commit") %}
 
       {% if relation_to_vacuum %}
           {% set start=modules.datetime.datetime.now() %}
           {{ dbt_utils.log_info(message_prefix ~ " Vacuuming " ~ relation_to_vacuum) }}
           {% do run_query("VACUUM " ~ relation_to_vacuum ~ " BOOST") %}
           {{ dbt_utils.log_info(message_prefix ~ " Analyzing " ~ relation_to_vacuum) }}
           {% do run_query("ANALYZE " ~ relation_to_vacuum) %}
           {% set end=modules.datetime.datetime.now() %}
           {% set total_seconds = (end - start).total_seconds() | round(2)  %}
           {{ dbt_utils.log_info(message_prefix ~ " Finished " ~ relation_to_vacuum ~ " in " ~ total_seconds ~ "s") }}
       {% else %}
           {{ dbt_utils.log_info(message_prefix ~ ' Skipping relation "' ~ row.values() | join ('"."') ~ '" as it does not exist') }}
       {% endif %}
 
   {% endfor %}
 
{% endmacro %}

Cloud DBT

Tha e comasach DBT a chleachdadh mar sheirbheis (Seirbheis Stiùiridh). Air a ghabhail a-steach:

  • IDE lìn airson pròiseactan agus modailean a leasachadh
  • Suidheachadh obrach agus clàr-ama
  • Cothrom sìmplidh agus goireasach air logaichean
  • Làrach-lìn le sgrìobhainnean mun phròiseact agad
  • A’ ceangal CI (Amalachadh leantainneach)

Inneal Togail Dàta no na tha cumanta eadar Data Warehouse agus Smoothie

co-dhùnadh

Bidh a bhith ag ullachadh agus ag ithe DWH a’ fàs cho tlachdmhor agus buannachdail ri bhith ag òl smoothie. Tha DBT air a dhèanamh suas de Jinja, leudachain luchd-cleachdaidh (modalan), compiler, neach-tiomnaidh, agus manaidsear pacaid. Le bhith a’ cur nan eileamaidean sin ri chèile gheibh thu àrainneachd obrach iomlan airson an Stòr-dàta agad. Cha mhòr gu bheil dòigh nas fheàrr air cruth-atharrachadh a riaghladh taobh a-staigh DWH an-diugh.

Inneal Togail Dàta no na tha cumanta eadar Data Warehouse agus Smoothie

Tha na creideasan a lean luchd-leasachaidh DBT air an cur ri chèile mar a leanas:

  • Is e còd, chan e GUI, an tarraing as fheàrr airson loidsig anailis iom-fhillte a chuir an cèill
  • Bu chòir a bhith ag obair le dàta na cleachdaidhean as fheàrr ann an innleadaireachd bathar-bog (Innleadaireachd Bathar-bog) atharrachadh.

  • Bu chòir don choimhearsnachd luchd-cleachdaidh smachd a chumail air bun-structar dàta èiginneach mar bhathar-bog stòr fosgailte
  • Bidh chan e a-mhàin innealan anailis, ach cuideachd còd gu bhith nan seilbh don choimhearsnachd Open Source

Tha na prìomh chreideasan sin air sìolachadh a dhèanamh air toradh a tha air a chleachdadh le còrr air 850 companaidh an-diugh, agus tha iad nam bunait airson mòran leudachadh inntinneach a thèid a chruthachadh san àm ri teachd.

Dhaibhsan aig a bheil ùidh, tha bhidio ann de leasan fosgailte a thug mi beagan mhìosan air ais mar phàirt de leasan fosgailte aig OTUS - Inneal Togail Dàta airson Stòradh Redshift Amazon.

A bharrachd air DBT agus Stòras Dàta, mar phàirt den chùrsa Einnseanair Dàta air àrd-ùrlar OTUS, bidh mo cho-obraichean agus mise a’ teagasg chlasaichean air grunn chuspairean buntainneach is ùr-nodha eile:

  • Bun-bheachdan Ailtireachd airson Tagraidhean Dàta Mòr
  • Cleachd le Spark and Spark Streaming
  • A’ sgrùdadh dhòighean agus innealan airson stòran dàta a luchdachadh
  • Togail taisbeanaidhean anailis ann an DWH
  • Bun-bheachdan NoSQL: HBase, Cassandra, ElasticSearch
  • Prionnsabalan sgrùdaidh agus orchestration 
  • Pròiseact Deireannach: a’ cur na sgilean gu lèir ri chèile fo thaic comhairleachaidh

Tùsan:

  1. Sgrìobhainnean DBT - Ro-ràdh - Sgrìobhainnean oifigeil
  2. Dè, dìreach, a th’ ann an dbt? — Dèan lèirmheas air artaigil le fear de na h-ùghdaran aig DBT 
  3. Inneal Togail Dàta airson Stòradh Redshift Amazon - YouTube, A’ clàradh leasan fosgailte OTUS
  4. A’ faighinn eòlas air Greenplum - Is e an ath leasan fosgailte 15 Cèitean, 2020
  5. Cùrsa innleadaireachd dàta — OTUS
  6. A’ togail sruth-obrach anailis aibidh - Sùil air àm ri teachd dàta agus anailis
  7. Tha an t-àm ann airson mion-sgrùdadh stòr fosgailte - Mean-fhàs anailis agus buaidh Open Source
  8. Amalachadh leantainneach agus deuchainn togail fèin-ghluasadach le dbtCloud - Prionnsabalan togail CI a’ cleachdadh DBT
  9. A’ tòiseachadh le oideachadh DBT - Cleachd, stiùireadh ceum air cheum airson obair neo-eisimeileach
  10. Bùth Jaffle - Github DBT Oideachadh - Github, còd pròiseict foghlaim

Ionnsaich tuilleadh mun chùrsa.

Source: www.habr.com

Cuir beachd ann